LeetCode 2 - Add Two Numbers
The problem gives us two non empty singly linked lists. Each linked list represents a non negative integer, but the digits are stored in reverse order. That means the head node contains the least significant digit.
Difficulty: 🟡 Medium
Topics: Linked List, Math, Recursion
Solution
Add Two Numbers
Problem Understanding
The problem gives us two non empty singly linked lists. Each linked list represents a non negative integer, but the digits are stored in reverse order.
That means the head node contains the least significant digit.
For example:
[2,4,3]represents the number342[5,6,4]represents the number465
The task is to add the two numbers together and return the result as another linked list, also stored in reverse order.
The result for the example above is:
342 + 465 = 807- Reverse order representation becomes
[7,0,8]
The important detail is that we are not supposed to convert the linked lists into actual integer values directly. While Python can handle very large integers, other languages may not, and the intended solution focuses on linked list manipulation and digit by digit addition.
The constraints tell us:
- Each list has between
1and100nodes - Each node contains a digit from
0to9 - No leading zeros exist, except for the number
0
The input size is relatively small, but the problem is designed to test linked list traversal and carry propagation logic.
Several edge cases matter here:
- Lists of different lengths, such as
[1,8]and[0] - Carry propagation across multiple digits, such as
[9,9,9] + [1] - Final carry after processing all nodes, such as
999 + 1 = 1000 - Inputs containing only zero
- One list ending earlier than the other
A naive implementation often fails when one list is shorter or when an extra carry remains after the final digit.
Approaches
Brute Force Approach
A straightforward approach is to reconstruct the two integers from the linked lists, add them together, then build a new linked list from the result.
The process works like this:
- Traverse the first linked list and compute the integer value.
- Traverse the second linked list and compute the integer value.
- Add the two integers.
- Convert the resulting sum back into a reversed linked list.
This approach is correct because it directly performs the arithmetic operation the problem asks for.
However, it has drawbacks:
- It depends on integer conversion.
- In some languages, the numbers could exceed built in integer limits.
- It ignores the linked list nature of the problem.
- It performs unnecessary number reconstruction.
Even though the constraints here are small enough that overflow is not a practical issue in Python, this is not considered the intended solution.
Optimal Approach
The better approach is to simulate elementary school addition digit by digit.
Since the digits are already stored in reverse order, we can process the linked lists from head to tail exactly the same way we add numbers manually from right to left.
At every step:
- Add the current digits from both lists
- Add any carry from the previous step
- Store the ones digit in the result list
- Carry the tens digit forward
This works naturally with linked lists and avoids reconstructing large integers entirely.
The key observation is that reverse order storage perfectly aligns with how addition normally proceeds.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n + m) | O(max(n, m)) | Converts linked lists into integers, then reconstructs result |
| Optimal | O(max(n, m)) | O(max(n, m)) | Adds digits directly while traversing lists |
Algorithm Walkthrough
- Create a dummy head node for the result list.
A dummy node simplifies linked list construction because we always append new nodes after it. This avoids special handling for the first node.
2. Initialize a pointer called current.
This pointer tracks the last node in the result list so new digits can be appended efficiently.
3. Initialize carry = 0.
Carry stores overflow from the previous digit addition. 4. Traverse both linked lists simultaneously.
Continue while at least one list still has nodes or a carry still exists. 5. Extract the current digit values.
If a list still has nodes, use its digit value. Otherwise use 0.
This handles lists of unequal length naturally. 6. Compute the digit sum.
Add:
- value from first list
- value from second list
- carry from previous step
- Update carry.
Use integer division:
carry = total // 10
This keeps only the overflow portion. 8. Create the current result digit.
Use modulo:
digit = total % 10
This extracts the current digit to store. 9. Append a new node to the result list.
Create a node with the computed digit and attach it after current.
10. Advance all pointers.
Move:
currentl1l2
- Return the real head.
Since the dummy node is artificial, return dummy.next.
Why it works
At every iteration, the algorithm correctly computes one digit of the final sum using the same carry based addition used in standard arithmetic.
Because the linked lists are stored in reverse order, the least significant digits appear first, allowing direct left to right traversal without reversing anything.
The invariant is:
- Before each iteration, the result list already contains the correct sum for all previously processed digits.
- The carry value correctly represents overflow into the next digit position.
When traversal ends, all digits and remaining carry have been processed, so the constructed linked list is the complete correct sum.
Python Solution
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
from typing import Optional
class Solution:
def addTwoNumbers(
self,
l1: Optional[ListNode],
l2: Optional[ListNode]
) -> Optional[ListNode]:
dummy = ListNode(0)
current = dummy
carry = 0
while l1 or l2 or carry:
value1 = l1.val if l1 else 0
value2 = l2.val if l2 else 0
total = value1 + value2 + carry
carry = total // 10
digit = total % 10
current.next = ListNode(digit)
current = current.next
if l1:
l1 = l1.next
if l2:
l2 = l2.next
return dummy.next
The implementation starts by creating a dummy head node. This node acts as a stable starting point for the result linked list.
The current pointer always refers to the tail of the result list. Every newly computed digit gets appended after it.
The loop continues while there are still digits remaining in either list or a carry still needs processing. This condition is critical because a final carry may create an additional node.
Inside the loop, missing digits are treated as zero when one list becomes shorter than the other. This keeps the addition logic uniform.
The sum is split into two parts:
digit, which becomes the current node valuecarry, which propagates into the next iteration
Finally, the function returns dummy.next because the dummy node itself is not part of the answer.
Go Solution
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
dummy := &ListNode{}
current := dummy
carry := 0
for l1 != nil || l2 != nil || carry != 0 {
value1 := 0
value2 := 0
if l1 != nil {
value1 = l1.Val
}
if l2 != nil {
value2 = l2.Val
}
total := value1 + value2 + carry
carry = total / 10
digit := total % 10
current.Next = &ListNode{Val: digit}
current = current.Next
if l1 != nil {
l1 = l1.Next
}
if l2 != nil {
l2 = l2.Next
}
}
return dummy.Next
}
The Go solution follows the same logic as the Python implementation.
The main language specific difference is pointer handling. Go uses nil instead of None, and linked list nodes are created using struct pointers.
Integer overflow is not a concern because every intermediate calculation stays within a very small range. The maximum digit sum is:
9 + 9 + 1 = 19
So standard integer arithmetic is completely safe.
Worked Examples
Example 1
Input:
l1 = [2,4,3]
l2 = [5,6,4]
These represent:
342 + 465
| Step | value1 | value2 | carry before | total | digit | carry after | Result List |
|---|---|---|---|---|---|---|---|
| 1 | 2 | 5 | 0 | 7 | 7 | 0 | [7] |
| 2 | 4 | 6 | 0 | 10 | 0 | 1 | [7,0] |
| 3 | 3 | 4 | 1 | 8 | 8 | 0 | [7,0,8] |
Final output:
[7,0,8]
Example 2
Input:
l1 = [0]
l2 = [0]
| Step | value1 | value2 | carry before | total | digit | carry after | Result List |
|---|---|---|---|---|---|---|---|
| 1 | 0 | 0 | 0 | 0 | 0 | 0 | [0] |
Final output:
[0]
Example 3
Input:
l1 = [9,9,9,9,9,9,9]
l2 = [9,9,9,9]
These represent:
9999999 + 9999
| Step | value1 | value2 | carry before | total | digit | carry after | Result List |
|---|---|---|---|---|---|---|---|
| 1 | 9 | 9 | 0 | 18 | 8 | 1 | [8] |
| 2 | 9 | 9 | 1 | 19 | 9 | 1 | [8,9] |
| 3 | 9 | 9 | 1 | 19 | 9 | 1 | [8,9,9] |
| 4 | 9 | 9 | 1 | 19 | 9 | 1 | [8,9,9,9] |
| 5 | 9 | 0 | 1 | 10 | 0 | 1 | [8,9,9,9,0] |
| 6 | 9 | 0 | 1 | 10 | 0 | 1 | [8,9,9,9,0,0] |
| 7 | 9 | 0 | 1 | 10 | 0 | 1 | [8,9,9,9,0,0,0] |
| 8 | 0 | 0 | 1 | 1 | 1 | 0 | [8,9,9,9,0,0,0,1] |
Final output:
[8,9,9,9,0,0,0,1]
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(max(n, m)) | Each node from both linked lists is visited once |
| Space | O(max(n, m)) | The output linked list stores the result digits |
The algorithm processes each node exactly once, so the running time grows linearly with the longer input list.
The auxiliary working memory is technically O(1), because only a few variables are maintained. However, the returned linked list itself requires space proportional to the number of digits in the result.
Test Cases
# Helper functions
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def build_list(values):
dummy = ListNode()
current = dummy
for value in values:
current.next = ListNode(value)
current = current.next
return dummy.next
def to_list(node):
result = []
while node:
result.append(node.val)
node = node.next
return result
solution = Solution()
# Example 1
l1 = build_list([2, 4, 3])
l2 = build_list([5, 6, 4])
assert to_list(solution.addTwoNumbers(l1, l2)) == [7, 0, 8] # basic addition
# Example 2
l1 = build_list([0])
l2 = build_list([0])
assert to_list(solution.addTwoNumbers(l1, l2)) == [0] # both zero
# Example 3
l1 = build_list([9, 9, 9, 9, 9, 9, 9])
l2 = build_list([9, 9, 9, 9])
assert to_list(solution.addTwoNumbers(l1, l2)) == [8, 9, 9, 9, 0, 0, 0, 1] # cascading carry
# Different lengths
l1 = build_list([1, 8])
l2 = build_list([0])
assert to_list(solution.addTwoNumbers(l1, l2)) == [1, 8] # shorter second list
# Final carry creation
l1 = build_list([9])
l2 = build_list([1])
assert to_list(solution.addTwoNumbers(l1, l2)) == [0, 1] # extra node from carry
# Multiple carry propagation
l1 = build_list([9, 9, 9])
l2 = build_list([1])
assert to_list(solution.addTwoNumbers(l1, l2)) == [0, 0, 0, 1] # carry through entire list
# No carry at all
l1 = build_list([1, 2, 3])
l2 = build_list([4, 5, 6])
assert to_list(solution.addTwoNumbers(l1, l2)) == [5, 7, 9] # simple digit sums
# One list much longer
l1 = build_list([1])
l2 = build_list([9, 9, 9, 9, 9])
assert to_list(solution.addTwoNumbers(l1, l2)) == [0, 0, 0, 0, 0, 1] # long carry chain
Test Case Summary
| Test | Why |
|---|---|
[2,4,3] + [5,6,4] |
Standard addition case |
[0] + [0] |
Smallest valid inputs |
[9,9,9,9,9,9,9] + [9,9,9,9] |
Carry propagation across many digits |
[1,8] + [0] |
Different list lengths |
[9] + [1] |
Final carry creates new node |
[9,9,9] + [1] |
Carry chain across entire list |
[1,2,3] + [4,5,6] |
No carry anywhere |
[1] + [9,9,9,9,9] |
One list significantly longer |
Edge Cases
Both Inputs Are Zero
The simplest valid input is:
[0] + [0]
A buggy implementation might accidentally return an empty list if it assumes zero digits should be skipped.
This implementation correctly creates a node with value 0 because the loop processes the digits normally even when the sum is zero.
Final Carry After Traversal
Cases like:
[9] + [1]
produce:
[0,1]
The most common mistake is stopping traversal immediately after both lists end, ignoring the remaining carry.
This implementation avoids that problem by continuing the loop while carry is non zero:
while l1 or l2 or carry:
That extra iteration creates the final node correctly.
Lists With Different Lengths
Inputs such as:
[1,8] + [0]
can break implementations that assume both lists always have nodes available simultaneously.
This solution handles missing nodes by treating them as zero:
value1 = l1.val if l1 else 0
value2 = l2.val if l2 else 0
That allows addition to continue correctly even after one list finishes earlier than the other.