Linked List
Simulate digit-by-digit addition through two linked lists, propagating carry at each step. Handle lists of different lengths and a final carry that produces a new node.
The grade-school addition algorithm, where you line up two numbers by their least significant digit, add column by column from right to left, and carry any overflow into the next column, translates almost directly into code. The challenge is expressing that pattern cleanly when numbers are represented as linked lists, reversed strings, or ordinary strings, while correctly handling unequal lengths and a final carry.
At each digit position you compute a local sum: the digit from the first number, plus the digit from the second number, plus whatever carry came in from the previous column. The result digit for this column is total % 10. The carry into the next column is total // 10, which is either 0 or 1 for single-digit inputs.
The loop condition while l1 or l2 or carry is the key to handling three edge cases in a single check. It continues as long as there are more digits in either number, or there is a pending carry that has not yet been written out. If you used while l1 and l2 you would need separate logic for unequal lengths and for a final carry like the one that turns 99 + 1 into 100.
Consider LeetCode 2. Add Two Numbers, which stores integers in reverse order, so 342 is stored as 2 -> 4 -> 3. This means the head of each list is already the least significant digit, so no reversal is needed before processing.
Adding 342 + 465:
Result list: 7 -> 0 -> 8, which represents 807 = 342 + 465. Correct.
Now consider 995 + 5, stored as 5 -> 9 -> 9 and 5:
Result: 0 -> 0 -> 0 -> 1, representing 1000 = 995 + 5. The final carry created an extra digit, which the or carry part of the while condition handled automatically.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def add_two_numbers(l1, l2):
dummy = ListNode(0)
curr = dummy
carry = 0
while l1 or l2 or carry:
val = carry
if l1:
val += l1.val
l1 = l1.next
if l2:
val += l2.val
l2 = l2.next
carry, digit = divmod(val, 10)
curr.next = ListNode(digit)
curr = curr.next
return dummy.nextThe dummy head node means the first result digit gets created the same way as every subsequent digit, curr.next = ListNode(digit), with no special case for the list being empty. At the end, dummy.next is the head of the result list.
Python's divmod(val, 10) returns (val // 10, val % 10), which is a concise way to split the carry and the digit in one line.
LeetCode 415. Add Strings gives two non-negative integers as strings and asks you to add them without converting to integers. The numbers are stored in the natural order (most significant digit first), so you process from the right end using two index pointers.
def add_strings(num1, num2):
i, j = len(num1) - 1, len(num2) - 1
carry = 0
result = []
while i >= 0 or j >= 0 or carry:
digit1 = int(num1[i]) if i >= 0 else 0
digit2 = int(num2[j]) if j >= 0 else 0
total = digit1 + digit2 + carry
carry, digit = divmod(total, 10)
result.append(str(digit))
i -= 1
j -= 1
return "".join(reversed(result))Because we append digits from least significant to most significant, we reverse result at the end. Alternatively, you can insert at position 0 each time, but that makes the overall algorithm due to repeated string shifts. Appending and reversing once at the end is .
The structure of this loop is identical to the linked list version. The only differences are syntactic: index arithmetic instead of node.next, and int(char) to extract digit values.
With LeetCode 43. Multiply Strings, the task is to multiply two non-negative integers given as strings. The key insight is that the digit at position i in num1 and the digit at position j in num2 contributes to position i + j in the result (with carry potentially going to i + j - 1 if you index from the right, or i + j + 1 if you index from the left).
def multiply(num1, num2):
m, n = len(num1), len(num2)
pos = [0] * (m + n) # result can have at most m + n digits
for i in range(m - 1, -1, -1):
for j in range(n - 1, -1, -1):
mul = int(num1[i]) * int(num2[j])
p1, p2 = i + j, i + j + 1 # p2 is the current digit position
total = mul + pos[p2]
pos[p2] = total % 10
pos[p1] += total // 10 # carry propagates to p1
# Convert to string, strip leading zeros
result = "".join(str(d) for d in pos).lstrip("0")
return result if result else "0"The outer loops iterate from the least significant digit inward (m-1 down to 0 and n-1 down to 0). pos[p2] holds the running digit at column i + j + 1, and pos[p1] accumulates the carry into column i + j. After all multiplications are done, the pos array holds the final digits, and you just strip leading zeros.
This is the same carry-propagation idea from column addition, extended to handle partial products. Each column can accumulate contributions from multiple digit pairs before carries are resolved.
For Add Two Numbers and Add Strings, the time complexity is where m and n are the lengths of the two inputs. Each digit is visited once. Space is for the output.
For Multiply Strings, the time complexity is due to the nested loops over all digit pairs. Space is for the result array.
You're probably looking at column addition with carry when:
"do not convert the inputs to integer directly").Common templates:
while a or b or carry loop, build a new list. Example: Add Two Numbers.i + j + 1, then resolve carries in one pass. Example: Multiply Strings.