Facebook Pixel

415. Add Strings

EasyMathStringSimulation
Leetcode Link

Problem Description

You are given two non-negative integers represented as strings, num1 and num2. Your task is to return the sum of these two numbers as a string.

The key constraints are:

  • You cannot use any built-in library designed for handling large integers (like BigInteger in Java)
  • You cannot directly convert the input strings to integers

This problem simulates manual addition that you would do on paper. You need to add the numbers digit by digit from right to left, keeping track of any carry values that occur when the sum of two digits exceeds 9.

For example:

  • If num1 = "123" and num2 = "456", the output should be "579"
  • If num1 = "99" and num2 = "1", the output should be "100" (note the carry propagation)

The solution implements this by:

  1. Starting from the rightmost digits of both strings
  2. Adding corresponding digits along with any carry from the previous position
  3. Recording the result digit (sum % 10) and updating the carry (sum / 10)
  4. Continuing until all digits are processed and there's no remaining carry
  5. Building the result string in reverse order, then reversing it at the end

The provided code also includes a bonus subStrings method that performs string subtraction using similar digit-by-digit processing with borrowing instead of carrying.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Think about how you learned to add numbers in elementary school. When adding two multi-digit numbers on paper, you start from the rightmost column (ones place) and work your way left. For each column, you add the digits and if the sum is 10 or greater, you write down the ones digit and carry the 1 to the next column.

Since we can't convert the strings to integers directly, we need to simulate this manual addition process character by character. The key insight is that we can extract individual digits from the strings using indexing and convert just single characters to integers (like int('5') gives us 5), which doesn't violate the constraint.

The challenge is handling strings of different lengths. When one number has more digits than the other, we essentially treat the missing digits as zeros. For instance, adding "999" and "1" is like adding "999" and "001".

We use two pointers starting from the end of each string because addition naturally flows from right to left due to the carry mechanism. The carry value c persists across iterations - if adding two digits gives us a sum ≥ 10, we need to remember to add that extra 1 to the next column.

The loop continues as long as:

  • There are still digits to process in num1 (i >= 0)
  • OR there are still digits to process in num2 (j >= 0)
  • OR there's still a carry value to propagate (c > 0)

Building the answer in reverse (appending digits to a list then reversing at the end) is more efficient than prepending to a string, as string concatenation in Python creates new string objects each time.

The divmod(a + b + c, 10) operation elegantly gives us both the new carry (quotient) and the digit to record (remainder) in one step, which is exactly what we need for each position in our manual addition.

Solution Approach

The solution uses a two-pointer technique to traverse both strings from right to left, simulating manual addition with carry propagation.

Implementation Details:

  1. Initialize pointers and variables:

    • Set i = len(num1) - 1 and j = len(num2) - 1 to point to the last digits of each string
    • Create an empty list ans to store result digits
    • Initialize carry c = 0
  2. Main addition loop:

    while i >= 0 or j >= 0 or c:

    This loop continues while there are digits to process OR there's a carry to handle.

  3. Extract digits safely:

    a = 0 if i < 0 else int(num1[i])
    b = 0 if j < 0 else int(num2[j])

    When one string runs out of digits, we treat missing positions as 0.

  4. Calculate sum and update carry:

    c, v = divmod(a + b + c, 10)
    • v gets the remainder (digit to store: (a + b + c) % 10)
    • c gets the quotient (new carry: (a + b + c) // 10)
  5. Store result and move pointers:

    ans.append(str(v))
    i, j = i - 1, j - 1

    Append the digit to our result list and move both pointers left.

  6. Build final string:

    return "".join(ans[::-1])

    Since we built the answer backwards (from least to most significant digit), we reverse it before joining.

For the bonus subtraction method subStrings:

The subtraction follows similar logic but with key differences:

  • First determines which number is larger to handle the sign of the result
  • Uses borrowing instead of carrying: c = 1 if c < 0 else 0
  • Calculates difference with borrowing: c = int(num1[i]) - c - int(num2[j])
  • Adds 10 and takes modulo to handle negative differences: (c + 10) % 10
  • Removes leading zeros from the result
  • Adds negative sign if the first number was smaller

Time Complexity: O(max(m, n)) where m and n are the lengths of the input strings, as we traverse each string once.

Space Complexity: O(1) ignoring the output string, as we only use a constant amount of extra variables.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's trace through adding num1 = "99" and num2 = "1" to see how the algorithm handles carry propagation.

Initial Setup:

  • i = 1 (pointing to last '9' in "99")
  • j = 0 (pointing to '1' in "1")
  • c = 0 (no initial carry)
  • ans = [] (empty result list)

Iteration 1: (rightmost column: 9 + 1)

  • i = 1, j = 0, c = 0
  • Extract digits: a = 9 (from num1[1]), b = 1 (from num2[0])
  • Calculate: 9 + 1 + 0 = 10
  • divmod(10, 10) gives us c = 1 (new carry), v = 0 (digit to store)
  • Append '0' to ans: ans = ['0']
  • Move pointers: i = 0, j = -1

Iteration 2: (middle column: 9 + 0 + carry)

  • i = 0, j = -1, c = 1
  • Extract digits: a = 9 (from num1[0]), b = 0 (j < 0, so use 0)
  • Calculate: 9 + 0 + 1 = 10
  • divmod(10, 10) gives us c = 1 (new carry), v = 0 (digit to store)
  • Append '0' to ans: ans = ['0', '0']
  • Move pointers: i = -1, j = -2

Iteration 3: (leftmost column: just the carry)

  • i = -1, j = -2, c = 1
  • Extract digits: a = 0 (i < 0, so use 0), b = 0 (j < 0, so use 0)
  • Calculate: 0 + 0 + 1 = 1
  • divmod(1, 10) gives us c = 0 (no more carry), v = 1 (digit to store)
  • Append '1' to ans: ans = ['0', '0', '1']
  • Move pointers: i = -2, j = -3

Loop exits because i < 0, j < 0, and c = 0

Final Step:

  • Reverse and join: ans[::-1] = ['1', '0', '0']
  • Return: "100"

The algorithm correctly handles the cascading carry, turning 99 + 1 into 100 by propagating the carry through multiple positions until it's fully resolved.

Solution Implementation

1class Solution:
2    def addStrings(self, num1: str, num2: str) -> str:
3        """
4        Add two non-negative integers represented as strings.
5      
6        Args:
7            num1: First number as string
8            num2: Second number as string
9          
10        Returns:
11            Sum of num1 and num2 as string
12        """
13        # Start from the rightmost digits
14        i, j = len(num1) - 1, len(num2) - 1
15        result = []
16        carry = 0
17      
18        # Process digits from right to left
19        while i >= 0 or j >= 0 or carry:
20            # Get current digits or 0 if we've exhausted that number
21            digit1 = 0 if i < 0 else int(num1[i])
22            digit2 = 0 if j < 0 else int(num2[j])
23          
24            # Calculate sum and new carry
25            carry, digit_sum = divmod(digit1 + digit2 + carry, 10)
26          
27            # Append current digit to result
28            result.append(str(digit_sum))
29          
30            # Move to next digits
31            i -= 1
32            j -= 1
33      
34        # Reverse the result since we built it backwards
35        return "".join(result[::-1])
36
37    def subStrings(self, num1: str, num2: str) -> str:
38        """
39        Subtract two non-negative integers represented as strings.
40      
41        Args:
42            num1: First number as string
43            num2: Second number as string
44          
45        Returns:
46            Difference of num1 - num2 as string
47        """
48        m, n = len(num1), len(num2)
49      
50        # Determine if result will be negative
51        is_negative = m < n or (m == n and num1 < num2)
52      
53        # Ensure we subtract smaller from larger (absolute value)
54        if is_negative:
55            num1, num2 = num2, num1
56      
57        # Start from the rightmost digits
58        i, j = len(num1) - 1, len(num2) - 1
59        result = []
60        borrow = 0
61      
62        # Process all digits of the larger number
63        while i >= 0:
64            # Get current digits
65            digit1 = int(num1[i])
66            digit2 = 0 if j < 0 else int(num2[j])
67          
68            # Perform subtraction with borrow
69            difference = digit1 - borrow - digit2
70          
71            # Handle negative difference by borrowing from next digit
72            result.append(str((difference + 10) % 10))
73            borrow = 1 if difference < 0 else 0
74          
75            # Move to next digits
76            i -= 1
77            j -= 1
78      
79        # Remove leading zeros (but keep at least one digit)
80        while len(result) > 1 and result[-1] == '0':
81            result.pop()
82      
83        # Add negative sign if needed
84        if is_negative:
85            result.append('-')
86      
87        # Reverse the result since we built it backwards
88        return ''.join(result[::-1])
89
1class Solution {
2    /**
3     * Add two non-negative integers represented as strings
4     * @param num1 First number as string
5     * @param num2 Second number as string
6     * @return Sum of num1 and num2 as string
7     */
8    public String addStrings(String num1, String num2) {
9        int i = num1.length() - 1;  // Pointer for num1, starting from rightmost digit
10        int j = num2.length() - 1;  // Pointer for num2, starting from rightmost digit
11        StringBuilder result = new StringBuilder();
12      
13        // Process digits from right to left, including carry
14        int carry = 0;
15        while (i >= 0 || j >= 0 || carry > 0) {
16            // Get current digit from num1, or 0 if we've exhausted num1
17            int digit1 = (i < 0) ? 0 : num1.charAt(i) - '0';
18            // Get current digit from num2, or 0 if we've exhausted num2
19            int digit2 = (j < 0) ? 0 : num2.charAt(j) - '0';
20          
21            // Calculate sum of current digits plus carry
22            carry += digit1 + digit2;
23            // Append the ones digit of the sum
24            result.append(carry % 10);
25            // Update carry for next iteration
26            carry /= 10;
27          
28            // Move pointers to next digits (towards left)
29            i--;
30            j--;
31        }
32      
33        // Reverse the result since we built it backwards
34        return result.reverse().toString();
35    }
36
37    /**
38     * Subtract two non-negative integers represented as strings
39     * @param num1 First number as string
40     * @param num2 Second number as string
41     * @return Difference (num1 - num2) as string, with negative sign if result is negative
42     */
43    public String subStrings(String num1, String num2) {
44        int len1 = num1.length();
45        int len2 = num2.length();
46      
47        // Determine if result will be negative (num1 < num2)
48        boolean isNegative = len1 < len2 || (len1 == len2 && num1.compareTo(num2) < 0);
49      
50        // Ensure num1 >= num2 for easier subtraction
51        if (isNegative) {
52            String temp = num1;
53            num1 = num2;
54            num2 = temp;
55        }
56      
57        int i = num1.length() - 1;  // Pointer for num1, starting from rightmost digit
58        int j = num2.length() - 1;  // Pointer for num2, starting from rightmost digit
59        StringBuilder result = new StringBuilder();
60      
61        // Process digits from right to left with borrowing
62        int borrow = 0;
63        while (i >= 0) {
64            // Get current digit from num2, or 0 if we've exhausted num2
65            int digit2 = (j < 0) ? 0 : num2.charAt(j) - '0';
66          
67            // Calculate difference: digit1 - borrow - digit2
68            int difference = (num1.charAt(i) - '0') - borrow - digit2;
69          
70            // If difference is negative, we need to borrow from next digit
71            result.append((difference + 10) % 10);
72            borrow = (difference < 0) ? 1 : 0;
73          
74            // Move pointers to next digits (towards left)
75            i--;
76            j--;
77        }
78      
79        // Remove leading zeros from the result
80        while (result.length() > 1 && result.charAt(result.length() - 1) == '0') {
81            result.deleteCharAt(result.length() - 1);
82        }
83      
84        // Add negative sign if needed
85        if (isNegative) {
86            result.append('-');
87        }
88      
89        // Reverse the result since we built it backwards
90        return result.reverse().toString();
91    }
92}
93
1class Solution {
2public:
3    /**
4     * Add two non-negative integers represented as strings
5     * @param num1 First number as string
6     * @param num2 Second number as string
7     * @return Sum of num1 and num2 as string
8     */
9    string addStrings(string num1, string num2) {
10        int i = num1.size() - 1;  // Index for traversing num1 from right to left
11        int j = num2.size() - 1;  // Index for traversing num2 from right to left
12        string result;
13      
14        // Process digits from right to left, including carry
15        for (int carry = 0; i >= 0 || j >= 0 || carry; --i, --j) {
16            // Get current digit from num1, or 0 if index out of bounds
17            int digit1 = i < 0 ? 0 : num1[i] - '0';
18            // Get current digit from num2, or 0 if index out of bounds
19            int digit2 = j < 0 ? 0 : num2[j] - '0';
20          
21            // Add digits and carry
22            carry += digit1 + digit2;
23            // Append the ones digit to result
24            result += to_string(carry % 10);
25            // Update carry for next iteration
26            carry /= 10;
27        }
28      
29        // Result was built in reverse order, so reverse it back
30        reverse(result.begin(), result.end());
31        return result;
32    }
33
34    /**
35     * Subtract two non-negative integers represented as strings
36     * @param num1 First number as string (minuend)
37     * @param num2 Second number as string (subtrahend)
38     * @return Difference of num1 - num2 as string (may include negative sign)
39     */
40    string subStrings(string num1, string num2) {
41        int m = num1.size();
42        int n = num2.size();
43      
44        // Check if result will be negative (num1 < num2)
45        bool isNegative = m < n || (m == n && num1 < num2);
46      
47        // If negative, swap to ensure we subtract smaller from larger
48        if (isNegative) {
49            swap(num1, num2);
50        }
51      
52        int i = num1.size() - 1;  // Index for traversing num1 from right to left
53        int j = num2.size() - 1;  // Index for traversing num2 from right to left
54        string result;
55      
56        // Process digits from right to left with borrow
57        for (int borrow = 0; i >= 0; --i, --j) {
58            // Get current digit from num2, or 0 if index out of bounds
59            int digit2 = j < 0 ? 0 : num2[j] - '0';
60          
61            // Subtract with borrow: (num1[i] - borrow - num2[j])
62            int diff = (num1[i] - '0') - borrow - digit2;
63          
64            // If diff is negative, add 10 and set borrow for next iteration
65            result += to_string((diff + 10) % 10);
66            borrow = diff < 0 ? 1 : 0;
67        }
68      
69        // Remove leading zeros from the result (built in reverse)
70        while (result.size() > 1 && result.back() == '0') {
71            result.pop_back();
72        }
73      
74        // Add negative sign if needed
75        if (isNegative) {
76            result.push_back('-');
77        }
78      
79        // Result was built in reverse order, so reverse it back
80        reverse(result.begin(), result.end());
81        return result;
82    }
83};
84
1/**
2 * Adds two non-negative integers represented as strings
3 * @param num1 - First number as string
4 * @param num2 - Second number as string
5 * @returns Sum of the two numbers as string
6 */
7function addStrings(num1: string, num2: string): string {
8    // Initialize pointers to the last digit of each number
9    let index1: number = num1.length - 1;
10    let index2: number = num2.length - 1;
11  
12    // Array to store the result digits in reverse order
13    const resultDigits: number[] = [];
14  
15    // Process digits from right to left with carry
16    let carry: number = 0;
17    while (index1 >= 0 || index2 >= 0 || carry > 0) {
18        // Add digit from num1 if available
19        if (index1 >= 0) {
20            carry += parseInt(num1[index1]);
21        }
22      
23        // Add digit from num2 if available
24        if (index2 >= 0) {
25            carry += parseInt(num2[index2]);
26        }
27      
28        // Store the current digit (remainder when divided by 10)
29        resultDigits.push(carry % 10);
30      
31        // Update carry for next iteration
32        carry = Math.floor(carry / 10);
33      
34        // Move to next digits
35        index1--;
36        index2--;
37    }
38  
39    // Reverse the result array and join to form the final string
40    return resultDigits.reverse().join('');
41}
42
43/**
44 * Subtracts two non-negative integers represented as strings
45 * @param num1 - First number as string
46 * @param num2 - Second number as string
47 * @returns Difference of the two numbers as string (can be negative)
48 */
49function subStrings(num1: string, num2: string): string {
50    const length1: number = num1.length;
51    const length2: number = num2.length;
52  
53    // Determine if the result will be negative
54    const isNegative: boolean = length1 < length2 || (length1 === length2 && num1 < num2);
55  
56    // Swap numbers if num1 is smaller than num2 to ensure we subtract smaller from larger
57    if (isNegative) {
58        const temp: string = num1;
59        num1 = num2;
60        num2 = temp;
61    }
62  
63    // Initialize pointers to the last digit of each number
64    let index1: number = num1.length - 1;
65    let index2: number = num2.length - 1;
66  
67    // Array to store the result digits in reverse order
68    const resultDigits: number[] = [];
69  
70    // Process digits from right to left with borrow
71    let borrow: number = 0;
72    while (index1 >= 0) {
73        // Get current digit from num1 and subtract borrow
74        let currentDifference: number = parseInt(num1[index1]) - borrow;
75      
76        // Subtract digit from num2 if available
77        if (index2 >= 0) {
78            currentDifference -= parseInt(num2[index2]);
79        }
80      
81        // Handle negative difference by borrowing from next digit
82        resultDigits.push((currentDifference + 10) % 10);
83      
84        // Update borrow for next iteration
85        borrow = currentDifference < 0 ? 1 : 0;
86      
87        // Move to next digits
88        index1--;
89        index2--;
90    }
91  
92    // Remove leading zeros from the result
93    while (resultDigits.length > 1 && resultDigits[resultDigits.length - 1] === 0) {
94        resultDigits.pop();
95    }
96  
97    // Add negative sign if needed and return the result
98    return (isNegative ? '-' : '') + resultDigits.reverse().join('');
99}
100

Time and Space Complexity

Time Complexity:

For addStrings: O(max(m, n)) where m is the length of num1 and n is the length of num2. The algorithm iterates through both strings once from right to left, processing each digit exactly once. The while loop runs for max(m, n) + 1 iterations in the worst case (when there's a final carry), and each iteration performs constant time operations including integer conversions, arithmetic operations, and string append.

For subStrings: O(max(m, n)) where m is the length of num1 and n is the length of num2. The algorithm first performs a comparison which takes O(min(m, n)) time in the worst case for string comparison. Then it iterates through the longer string once, performing constant time operations per digit. The final cleanup loop to remove leading zeros runs at most O(max(m, n)) times.

Space Complexity:

For addStrings: O(max(m, n)) for the ans list which stores the result digits. The result can have at most max(m, n) + 1 digits (when there's a carry from the most significant digit). The additional variables i, j, c, a, b, and v use O(1) space.

For subStrings: O(max(m, n)) for the ans list which stores the result digits. The result can have at most max(m, n) digits. The additional variables including neg, i, j, and c use O(1) space. Note that when swapping num1 and num2, no additional space is used as Python just swaps references.

Common Pitfalls

1. Forgetting to Handle the Final Carry

One of the most common mistakes is forgetting that after processing all digits, there might still be a carry that needs to be added to the result.

Incorrect Implementation:

def addStrings(self, num1: str, num2: str) -> str:
    i, j = len(num1) - 1, len(num2) - 1
    result = []
    carry = 0
  
    # Bug: Only loops while there are digits, ignores final carry
    while i >= 0 or j >= 0:  # Missing "or carry"
        digit1 = 0 if i < 0 else int(num1[i])
        digit2 = 0 if j < 0 else int(num2[j])
        carry, digit_sum = divmod(digit1 + digit2 + carry, 10)
        result.append(str(digit_sum))
        i -= 1
        j -= 1
  
    return "".join(result[::-1])

# This fails for cases like "99" + "1" 
# Expected: "100", but returns "00" (missing the final '1')

Solution: Always include or carry in the loop condition to ensure the final carry is processed.

2. Building the Result in the Wrong Order

Another frequent error is forgetting that when we process digits from right to left, we're building the result backwards and need to reverse it.

Incorrect Implementation:

def addStrings(self, num1: str, num2: str) -> str:
    i, j = len(num1) - 1, len(num2) - 1
    result = []
    carry = 0
  
    while i >= 0 or j >= 0 or carry:
        digit1 = 0 if i < 0 else int(num1[i])
        digit2 = 0 if j < 0 else int(num2[j])
        carry, digit_sum = divmod(digit1 + digit2 + carry, 10)
        result.append(str(digit_sum))
        i -= 1
        j -= 1
  
    # Bug: Forgot to reverse the result
    return "".join(result)  # Missing [::-1]

# For "123" + "456", this returns "975" instead of "579"

Solution: Always reverse the result list before joining, or alternatively, insert digits at the beginning of the result (though this is less efficient).

3. Incorrect Handling of Leading Zeros in Subtraction

When implementing subtraction, failing to remove leading zeros can produce incorrect-looking results.

Incorrect Implementation:

def subStrings(self, num1: str, num2: str) -> str:
    # ... (setup code)
  
    # Process subtraction...
    while i >= 0:
        # ... (subtraction logic)
        result.append(str((difference + 10) % 10))
        # ...
  
    # Bug: Forgot to remove leading zeros
    # If we subtract "100" - "99", we might get "001" instead of "1"
  
    if is_negative:
        result.append('-')
  
    return ''.join(result[::-1])

Solution: Always strip leading zeros after subtraction, but ensure at least one digit remains (for the case when the result is "0").

4. Confusing Carry Logic with Borrow Logic

When implementing both addition and subtraction, it's easy to mix up the carry/borrow update logic.

Incorrect Implementation:

def subStrings(self, num1: str, num2: str) -> str:
    # ...
    borrow = 0
  
    while i >= 0:
        digit1 = int(num1[i])
        digit2 = 0 if j < 0 else int(num2[j])
      
        difference = digit1 - borrow - digit2
      
        # Bug: Using addition carry logic for subtraction
        borrow, digit = divmod(difference, 10)  # Wrong!
        # This doesn't handle negative differences correctly
      
        result.append(str(digit))
        i -= 1
        j -= 1

Solution: For subtraction, explicitly check if the difference is negative and set borrow accordingly:

result.append(str((difference + 10) % 10))
borrow = 1 if difference < 0 else 0
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of the following shows the order of node visit in a Breadth-first Search?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More