415. Add Strings
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"
andnum2 = "456"
, the output should be"579"
- If
num1 = "99"
andnum2 = "1"
, the output should be"100"
(note the carry propagation)
The solution implements this by:
- Starting from the rightmost digits of both strings
- Adding corresponding digits along with any carry from the previous position
- Recording the result digit (sum % 10) and updating the carry (sum / 10)
- Continuing until all digits are processed and there's no remaining carry
- 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.
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:
-
Initialize pointers and variables:
- Set
i = len(num1) - 1
andj = 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
- Set
-
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.
-
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.
-
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
)
-
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.
-
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 EvaluatorExample 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 usc = 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 usc = 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 usc = 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
Which of the following shows the order of node visit in a Breadth-first Search?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!