-1

The question concerns LeetCode challenge #2:

You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order, and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list.

Input: l1 = [2,4,3], l2 = [5,6,4]      
Output: [7,0,8]  
Explanation: 342 + 465 = 807.

I compared the following two solutions:

Algorithm with space complexity O(1)

public ListNode addTwoNumbers(ListNode l1, ListNode l2) {   
    ListNode l1Pointer = l1;
    ListNode l2Pointer = l2;
    
    while(l1Pointer != null){
        if(l2Pointer != null)
            l1Pointer.val += l2Pointer.val;
        if(l1Pointer.val >= 10){
            l1Pointer.val -= 10;
            if(l1Pointer.next != null) l1Pointer.next.val++;
            else if(l2Pointer != null && l2Pointer.next != null) l2Pointer.next.val++;
            else l1Pointer.next = new ListNode(1);
        }
        if(l1Pointer.next == null && l2Pointer != null && l2Pointer.next != null){
            l1Pointer.next = l2Pointer.next;
            l2Pointer.next = null;
        }
        l1Pointer = l1Pointer.next;
        if(l2Pointer != null)
            l2Pointer = l2Pointer.next;
    }
    return l1;
}

and it uses more than 39000kb memory:

enter image description here

Algorithm with space complexity O(n)

public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    ListNode c1 = l1;
    ListNode c2 = l2;
    ListNode sentinel = new ListNode(0);
    ListNode d = sentinel;
    int sum = 0;
    while (c1 != null || c2 != null) {
        sum /= 10;
        if (c1 != null) {
            sum += c1.val;
            c1 = c1.next;
        }
        if (c2 != null) {
            sum += c2.val;
            c2 = c2.next;
        }
        d.next = new ListNode(sum % 10);
        d = d.next;
    }
    if (sum / 10 == 1)
        d.next = new ListNode(1);
    return sentinel.next;
}

and it uses less than 39000kb memory:

enter image description here

I'm very confused. Did I make a mistake in the calculations of the space complexities?

trincot
  • 317,000
  • 35
  • 244
  • 286
  • Can you also mention both the algorithms used? It can help in analysis. – AKSingh Apr 17 '21 at 09:46
  • 1
    What's the list size? Each node would occupy about 12 bytes and you're reporting 39MB, just adding 100 nodes won't make any difference. – sonus21 Apr 17 '21 at 09:57
  • I would take the memory usage report from leetcode with a grain of salt. There are aspects like the garbage collection which may influence these statistics such that even the same code will sometimes get its memory usage reported differently. – trincot Apr 17 '21 at 11:43
  • 1
    @trincot Yeah, I submitted both and got the opposite results. Before asking "why", first make sure it's actually true... – Manuel Apr 17 '21 at 13:00

1 Answers1

1

You should not rely too much on these memory usage reports. For Java there are aspects that are not under your control which still influence the memory usage. For instance the garbage collector decides on its own when to reclaim memory as free. Also parsing your code requires memory, and it is not sure that your code gets to run in an environment that is exclusively dedicated to running your program. Certainly the testing code is also running in that same environment. If some tests are random such that the memory footprint is also dependent on it, then that is yet another factor that plays a role.

You may find that submitting the same code several times will lead to slightly different memory reports.

What is important in those reports is the whether your solution scores relatively close to other efficient solutions and does not use like 50% more memory. In your examples we see these reports are relatively close, less than 1% difference. If you would print them on a graph that has its X axis start at 0, you would really notice how very close they are. This margin is negligible and also variable from run to run.

As to the algorithms:

It is true that one uses O(1) auxiliary memory and the other O() auxiliary memory. But the first one destroys the original input lists, using the first list for storing the result. This is not very nice in a real life situation. The caller may want to continue using the original lists, and will be surprised to see that the list that is returned is actually not a new list, but is the first list that has been mutated. Also the second list will often be mutated to something unrecognisable from what it was. This is not clean at all.

So, please go for the O() algorithm and don't take the memory usage report on LeetCode too seriously.

trincot
  • 317,000
  • 35
  • 244
  • 286