Skip to content

Arithmetic Operations on Linked Lists

Adding, subtracting, and multiplying numbers represented as linked lists


Overview

Arithmetic operations on linked lists involve performing mathematical operations on numbers where each digit is stored in a separate node. These problems test your ability to handle carries, different number lengths, and various digit orderings.

Arithmetic Operations Visualization


Add Two Numbers (LeetCode #2)

Problem Statement

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

Solution

python
def addTwoNumbers(l1: ListNode, l2: ListNode) -> ListNode:
    """
    Add two numbers stored in reverse order.
    Process digit by digit, handling carry.

    Example: 342 + 465 = 807
    L1: 2 -> 4 -> 3 (342 reversed)
    L2: 5 -> 6 -> 4 (465 reversed)
    Result: 7 -> 0 -> 8 (807 reversed)
    """
    dummy = ListNode(0)
    current = dummy
    carry = 0

    while l1 or l2 or carry:
        # Get values (0 if list exhausted)
        val1 = l1.val if l1 else 0
        val2 = l2.val if l2 else 0

        # Calculate sum and carry
        total = val1 + val2 + carry
        carry = total // 10
        digit = total % 10

        # Create new node
        current.next = ListNode(digit)
        current = current.next

        # Advance pointers
        l1 = l1.next if l1 else None
        l2 = l2.next if l2 else None

    return dummy.next
java
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    ListNode dummy = new ListNode(0);
    ListNode current = dummy;
    int carry = 0;

    while (l1 != null || l2 != null || carry != 0) {
        int val1 = (l1 != null) ? l1.val : 0;
        int val2 = (l2 != null) ? l2.val : 0;

        int total = val1 + val2 + carry;
        carry = total / 10;
        int digit = total % 10;

        current.next = new ListNode(digit);
        current = current.next;

        if (l1 != null) l1 = l1.next;
        if (l2 != null) l2 = l2.next;
    }

    return dummy.next;
}

Complexity: Time O(max(m, n)) · Space O(max(m, n))

  • Time: O(max(m, n)) because we traverse both lists in parallel until both are exhausted and carry is zero
  • Space: O(max(m, n)) because we create a new node for each digit of the result

Step-by-Step Example

L1: 2 -> 4 -> 3 (represents 342)
L2: 5 -> 6 -> 4 (represents 465)

Step 1: 2 + 5 = 7, carry = 0
Step 2: 4 + 6 + 0 = 10, digit = 0, carry = 1
Step 3: 3 + 4 + 1 = 8, carry = 0

Result: 7 -> 0 -> 8 (represents 807)

Complexity

MetricValue
TimeO(max(m, n))
SpaceO(max(m, n))

Add Two Numbers II (LeetCode #445)

Problem Statement

Same as above, but digits are stored in normal order (most significant digit first).

Approach 1: Using Stacks

python
def addTwoNumbers_stacks(l1: ListNode, l2: ListNode) -> ListNode:
    """
    Use stacks to process from right to left.
    Build result from head (insert at front).
    """
    # Push all digits to stacks
    stack1, stack2 = [], []

    while l1:
        stack1.append(l1.val)
        l1 = l1.next
    while l2:
        stack2.append(l2.val)
        l2 = l2.next

    # Add from right to left
    carry = 0
    head = None

    while stack1 or stack2 or carry:
        val1 = stack1.pop() if stack1 else 0
        val2 = stack2.pop() if stack2 else 0

        total = val1 + val2 + carry
        carry = total // 10
        digit = total % 10

        # Insert at front
        new_node = ListNode(digit)
        new_node.next = head
        head = new_node

    return head
java
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    Deque<Integer> stack1 = new ArrayDeque<>();
    Deque<Integer> stack2 = new ArrayDeque<>();

    while (l1 != null) { stack1.push(l1.val); l1 = l1.next; }
    while (l2 != null) { stack2.push(l2.val); l2 = l2.next; }

    int carry = 0;
    ListNode head = null;

    while (!stack1.isEmpty() || !stack2.isEmpty() || carry != 0) {
        int val1 = stack1.isEmpty() ? 0 : stack1.pop();
        int val2 = stack2.isEmpty() ? 0 : stack2.pop();

        int total = val1 + val2 + carry;
        carry = total / 10;
        int digit = total % 10;

        ListNode newNode = new ListNode(digit);
        newNode.next = head;
        head = newNode;
    }

    return head;
}

Complexity: Time O(m + n) · Space O(m + n)

  • Time: O(m + n) because we push all digits to stacks and then process them
  • Space: O(m + n) for the two stacks storing all digits from both input lists

Approach 2: Reverse, Add, Reverse

python
def addTwoNumbers_reverse(l1: ListNode, l2: ListNode) -> ListNode:
    """
    1. Reverse both lists
    2. Add like problem #2
    3. Reverse result
    """
    def reverse(head):
        prev = None
        curr = head
        while curr:
            next_temp = curr.next
            curr.next = prev
            prev = curr
            curr = next_temp
        return prev

    # Reverse inputs
    l1 = reverse(l1)
    l2 = reverse(l2)

    # Add (same as problem #2)
    dummy = ListNode(0)
    current = dummy
    carry = 0

    while l1 or l2 or carry:
        val1 = l1.val if l1 else 0
        val2 = l2.val if l2 else 0

        total = val1 + val2 + carry
        carry = total // 10

        current.next = ListNode(total % 10)
        current = current.next

        l1 = l1.next if l1 else None
        l2 = l2.next if l2 else None

    # Reverse result
    return reverse(dummy.next)

Complexity: Time O(m + n) · Space O(max(m, n))

  • Time: O(m + n) because we reverse both lists (O(m + n)), add them (O(max(m, n))), and reverse result
  • Space: O(max(m, n)) for the result list; reversals are done in-place

Approach 3: Recursive with Padding

python
def addTwoNumbers_recursive(l1: ListNode, l2: ListNode) -> ListNode:
    """
    Pad shorter list, then recurse to add from right.
    """
    def get_length(head):
        length = 0
        while head:
            length += 1
            head = head.next
        return length

    def pad_list(head, padding):
        for _ in range(padding):
            new_node = ListNode(0)
            new_node.next = head
            head = new_node
        return head

    def add_helper(l1, l2):
        """Returns (result_node, carry)"""
        if not l1:
            return None, 0

        # Recurse first
        next_node, carry = add_helper(l1.next, l2.next)

        # Add current digits
        total = l1.val + l2.val + carry
        node = ListNode(total % 10)
        node.next = next_node

        return node, total // 10

    # Get lengths
    len1, len2 = get_length(l1), get_length(l2)

    # Pad shorter list
    if len1 < len2:
        l1 = pad_list(l1, len2 - len1)
    else:
        l2 = pad_list(l2, len1 - len2)

    # Add recursively
    result, carry = add_helper(l1, l2)

    # Handle final carry
    if carry:
        new_head = ListNode(carry)
        new_head.next = result
        result = new_head

    return result

Complexity: Time O(m + n) · Space O(max(m, n))

  • Time: O(m + n) because we calculate lengths, pad the shorter list, and recursively add
  • Space: O(max(m, n)) for the recursion stack and the result list

Complexity Comparison

ApproachTimeSpace
StacksO(m + n)O(m + n)
ReverseO(m + n)O(max(m, n)) for result
RecursiveO(m + n)O(max(m, n))

Subtract Two Numbers

Problem Statement

Subtract smaller number from larger number represented as linked lists.

python
def subtractTwoNumbers(l1: ListNode, l2: ListNode) -> ListNode:
    """
    Subtract l2 from l1 (assuming l1 >= l2).
    Handle borrowing instead of carrying.
    """
    # First determine which is larger
    def to_number(head):
        num = 0
        while head:
            num = num * 10 + head.val
            head = head.next
        return num

    num1, num2 = to_number(l1), to_number(l2)

    # Ensure we subtract smaller from larger
    if num1 < num2:
        l1, l2 = l2, l1

    # Reverse lists for easier processing
    def reverse(head):
        prev = None
        while head:
            next_temp = head.next
            head.next = prev
            prev = head
            head = next_temp
        return prev

    l1, l2 = reverse(l1), reverse(l2)

    dummy = ListNode(0)
    current = dummy
    borrow = 0

    while l1:
        val1 = l1.val - borrow
        val2 = l2.val if l2 else 0

        if val1 < val2:
            val1 += 10
            borrow = 1
        else:
            borrow = 0

        current.next = ListNode(val1 - val2)
        current = current.next

        l1 = l1.next
        l2 = l2.next if l2 else None

    # Reverse result and remove leading zeros
    result = reverse(dummy.next)
    while result and result.next and result.val == 0:
        result = result.next

    return result
java
public ListNode subtractTwoNumbers(ListNode l1, ListNode l2) {
    long num1 = toNumber(l1);
    long num2 = toNumber(l2);

    if (num1 < num2) { ListNode tmp = l1; l1 = l2; l2 = tmp; }

    l1 = reverse(l1);
    l2 = reverse(l2);

    ListNode dummy = new ListNode(0);
    ListNode current = dummy;
    int borrow = 0;

    while (l1 != null) {
        int val1 = l1.val - borrow;
        int val2 = (l2 != null) ? l2.val : 0;

        if (val1 < val2) { val1 += 10; borrow = 1; }
        else { borrow = 0; }

        current.next = new ListNode(val1 - val2);
        current = current.next;

        l1 = l1.next;
        if (l2 != null) l2 = l2.next;
    }

    ListNode result = reverse(dummy.next);
    while (result != null && result.next != null && result.val == 0) {
        result = result.next;
    }
    return result;
}

private long toNumber(ListNode head) {
    long num = 0;
    while (head != null) { num = num * 10 + head.val; head = head.next; }
    return num;
}

private ListNode reverse(ListNode head) {
    ListNode prev = null;
    while (head != null) {
        ListNode next = head.next;
        head.next = prev;
        prev = head;
        head = next;
    }
    return prev;
}

Complexity: Time O(m + n) · Space O(max(m, n))

  • Time: O(m + n) because we traverse both lists to convert to numbers, then reverse and process
  • Space: O(max(m, n)) for the result list; intermediate operations use constant space

Multiply Two Numbers

Problem Statement

Multiply two numbers represented as linked lists.

python
def multiplyTwoNumbers(l1: ListNode, l2: ListNode) -> ListNode:
    """
    Multiply digit by digit, similar to long multiplication.
    """
    def reverse(head):
        prev = None
        while head:
            next_temp = head.next
            head.next = prev
            prev = head
            head = next_temp
        return prev

    def to_list(num):
        if num == 0:
            return ListNode(0)
        head = None
        while num > 0:
            node = ListNode(num % 10)
            node.next = head
            head = node
            num //= 10
        return head

    # Convert to numbers (for simplicity)
    # For very large numbers, use array-based multiplication
    def to_number(head):
        num = 0
        while head:
            num = num * 10 + head.val
            head = head.next
        return num

    num1, num2 = to_number(l1), to_number(l2)
    return to_list(num1 * num2)
java
public ListNode multiplyTwoNumbers(ListNode l1, ListNode l2) {
    long num1 = toNumber(l1);
    long num2 = toNumber(l2);
    return toList(num1 * num2);
}

private long toNumber(ListNode head) {
    long num = 0;
    while (head != null) { num = num * 10 + head.val; head = head.next; }
    return num;
}

private ListNode toList(long num) {
    if (num == 0) return new ListNode(0);
    ListNode head = null;
    while (num > 0) {
        ListNode node = new ListNode((int)(num % 10));
        node.next = head;
        head = node;
        num /= 10;
    }
    return head;
}

Complexity: Time O(m * n) · Space O(max(m, n))

  • Time: O(m + n) to convert the lists to numbers, but the multiplication itself dominates: schoolbook multiply of an m-digit and an n-digit number is O(m * n) (Python's big-int multiply uses Karatsuba, roughly O(n^1.585))
  • Space: O(max(m, n)) for the result list; may overflow for languages without big integer support

Array-Based Multiplication (For Large Numbers)

python
def multiplyTwoNumbers_array(l1: ListNode, l2: ListNode) -> ListNode:
    """
    Handle very large numbers using array-based multiplication.
    Similar to multiplying polynomials.
    """
    # Convert lists to arrays
    def to_array(head):
        arr = []
        while head:
            arr.append(head.val)
            head = head.next
        return arr

    arr1, arr2 = to_array(l1), to_array(l2)
    n1, n2 = len(arr1), len(arr2)

    # Result array
    result = [0] * (n1 + n2)

    # Multiply
    for i in range(n1 - 1, -1, -1):
        for j in range(n2 - 1, -1, -1):
            mul = arr1[i] * arr2[j]
            p1, p2 = i + j, i + j + 1
            total = mul + result[p2]

            result[p2] = total % 10
            result[p1] += total // 10

    # Convert to linked list
    dummy = ListNode(0)
    current = dummy
    leading_zero = True

    for digit in result:
        if digit == 0 and leading_zero:
            continue
        leading_zero = False
        current.next = ListNode(digit)
        current = current.next

    return dummy.next if dummy.next else ListNode(0)

Complexity: Time O(m * n) · Space O(m + n)

  • Time: O(m * n) because we multiply each digit pair, similar to grade-school multiplication algorithm
  • Space: O(m + n) for the result array and linked list; handles arbitrarily large numbers

Common Patterns

Carry Handling Pattern

python
carry = 0
while l1 or l2 or carry:
    val1 = l1.val if l1 else 0
    val2 = l2.val if l2 else 0

    total = val1 + val2 + carry
    carry = total // 10
    digit = total % 10

    # Process digit...

    l1 = l1.next if l1 else None
    l2 = l2.next if l2 else None

Build Result from Head (Insert at Front)

python
head = None
while condition:
    new_node = ListNode(value)
    new_node.next = head
    head = new_node

Edge Cases

  1. Different lengths: Handle shorter list exhausting first
  2. Carry overflow: Final carry creating new digit
  3. Zero result: Single node with value 0
  4. Leading zeros: Remove from result (except single 0)
  5. Large numbers: May overflow standard integer types

Add Two Numbers (LeetCode 2)

Problem: Add two numbers stored in reverse order as linked lists. Return sum as a linked list.

Key Insight: Process from head (least significant digit), propagate carry forward.

Approach: Iterate both lists, add values with carry, create node for digit, propagate carry.

Complexity: O(max(m,n)) time, O(max(m,n)) space

Add Two Numbers II (LeetCode 445)

Problem: Add two numbers stored in normal order (most significant first) as linked lists.

Key Insight: Use stacks to process from right to left, or reverse both lists first.

Approach: Push all digits to stacks, pop and add from right, insert result at head.

Complexity: O(m+n) time, O(m+n) space

Plus One Linked List (LeetCode 369)

Problem: Represent a number as a linked list (most significant first), add one to it.

Key Insight: Find rightmost non-9 digit, increment it, set all following 9s to 0.

Approach: Find rightmost non-9 digit, increment it, zero out all digits to its right. Handle all-9s case.

Complexity: O(n) time, O(1) space

Multiply Strings (LeetCode 43)

Problem: Multiply two non-negative integers represented as strings.

Key Insight: Use grade-school multiplication with position-based indexing.

Approach: For each pair of digits, multiply and add to result at position i+j and i+j+1.

Complexity: O(m*n) time, O(m+n) space


Interview Tips

  1. Clarify digit order: Reverse vs normal order changes approach
  2. Handle carry: Always check for final carry
  3. Use dummy node: Simplifies list construction
  4. Watch for lengths: Different length handling is crucial
  5. Consider overflow: For very large numbers, use array-based approach

Sources