Files
codetutor/backend/data/questions/add-strings.yaml

198 lines
8.1 KiB
YAML

title: Add Strings
slug: add-strings
difficulty: easy
leetcode_id: 415
leetcode_url: https://leetcode.com/problems/add-strings/
categories:
- strings
- math
patterns:
- two-pointers
function_signature: "def add_strings(num1: str, num2: str) -> str:"
test_cases:
visible:
- input: { num1: "11", num2: "123" }
expected: "134"
- input: { num1: "456", num2: "77" }
expected: "533"
- input: { num1: "0", num2: "0" }
expected: "0"
hidden:
- input: { num1: "99", num2: "1" }
expected: "100"
- input: { num1: "1", num2: "9" }
expected: "10"
- input: { num1: "999", num2: "999" }
expected: "1998"
description: |
Given two non-negative integers, `num1` and `num2` represented as strings, return *the sum of* `num1` *and* `num2` *as a string*.
You must solve the problem without using any built-in library for handling large integers (such as `BigInteger`). You must also not convert the inputs to integers directly.
constraints: |
- `1 <= num1.length, num2.length <= 10^4`
- `num1` and `num2` consist of only digits.
- `num1` and `num2` don't have any leading zeros except for the zero itself.
examples:
- input: 'num1 = "11", num2 = "123"'
output: '"134"'
explanation: "11 + 123 = 134"
- input: 'num1 = "456", num2 = "77"'
output: '"533"'
explanation: "456 + 77 = 533"
- input: 'num1 = "0", num2 = "0"'
output: '"0"'
explanation: "0 + 0 = 0"
explanation:
intuition: |
Think back to how you learned to add numbers by hand in primary school. You start from the **rightmost digits**, add them together, write down the result, and carry over any overflow to the next column.
This problem asks us to simulate exactly that process. Since we can't convert the strings to integers directly (which would fail for very large numbers anyway), we process the strings **digit by digit from right to left**, just like manual addition.
The key insight is that each digit position can be handled independently: add the two digits plus any carry from the previous position, then determine the new carry and the digit to write. This mechanical process works regardless of how long the numbers are.
approach: |
We simulate elementary school addition using **two pointers** starting from the end of each string:
**Step 1: Initialise variables**
- `i`: Pointer to the last character of `num1`
- `j`: Pointer to the last character of `num2`
- `carry`: Set to `0` to track overflow between digit additions
- `result`: Empty list to collect the result digits (we'll reverse at the end)
&nbsp;
**Step 2: Process digits from right to left**
- While either pointer is valid OR there's a remaining carry:
- Get digit from `num1[i]` if `i >= 0`, otherwise use `0`
- Get digit from `num2[j]` if `j >= 0`, otherwise use `0`
- Calculate `total = digit1 + digit2 + carry`
- Append `total % 10` to result (the digit to keep)
- Update `carry = total // 10` (either `0` or `1`)
- Decrement both pointers
&nbsp;
**Step 3: Build the final result**
- Reverse the result list (we built it backwards)
- Join into a string and return
&nbsp;
The two-pointer approach handles strings of different lengths naturally by treating missing digits as zeros.
common_pitfalls:
- title: Converting to Integer
description: |
The most obvious approach is to convert both strings to integers, add them, and convert back:
```python
return str(int(num1) + int(num2)) # Violates the rules!
```
This violates the problem constraints. More importantly, it would fail for numbers larger than what the language's integer type can handle. The digit-by-digit approach works for arbitrarily large numbers.
wrong_approach: "int(num1) + int(num2)"
correct_approach: "Digit-by-digit simulation"
- title: Processing Left to Right
description: |
If you try to add digits from left to right (index 0 onwards), you'll have problems:
- The carry propagates in the wrong direction
- Strings of different lengths don't align properly
For example, adding `"99"` and `"1"`: starting from the left, you'd try to add `9 + 1 = 10`, but the carry needs to affect digits to the *left*, not right.
Always process from **right to left**, then reverse the result.
wrong_approach: "Iterate from index 0"
correct_approach: "Iterate from the last index backwards"
- title: Forgetting the Final Carry
description: |
After processing all digits, there might still be a carry left over. For example, `"99" + "1"` produces digits `0, 0` with a final carry of `1`.
If you don't check for this remaining carry, you'd return `"00"` instead of `"100"`.
The loop condition `while i >= 0 or j >= 0 or carry` handles this by continuing while there's a carry to process.
key_takeaways:
- "**Simulate manual processes**: When built-in operations are forbidden, think about how you'd solve it by hand"
- "**Two-pointer from the end**: For digit-by-digit arithmetic, start from the least significant digit (rightmost)"
- "**Handle different lengths gracefully**: Treat missing digits as zero rather than special-casing length differences"
- "**Same pattern for multiplication**: This digit-by-digit approach extends to string multiplication (LeetCode 43)"
time_complexity: "O(max(n, m)). We process each digit exactly once, where `n` and `m` are the lengths of `num1` and `num2`."
space_complexity: "O(max(n, m)). The result string has at most `max(n, m) + 1` digits (one extra for a potential leading carry)."
solutions:
- approach_name: Two Pointers with Carry
is_optimal: true
code: |
def add_strings(num1: str, num2: str) -> str:
# Start from the rightmost digit of each string
i, j = len(num1) - 1, len(num2) - 1
carry = 0
result = []
# Process while there are digits or a carry remains
while i >= 0 or j >= 0 or carry:
# Get current digits (0 if we've exhausted that string)
digit1 = int(num1[i]) if i >= 0 else 0
digit2 = int(num2[j]) if j >= 0 else 0
# Add digits and carry
total = digit1 + digit2 + carry
# Keep the ones digit, carry the tens digit
result.append(str(total % 10))
carry = total // 10
# Move to the next digit (leftward)
i -= 1
j -= 1
# We built the result backwards, so reverse it
return ''.join(reversed(result))
explanation: |
**Time Complexity:** O(max(n, m)) — Single pass through both strings.
**Space Complexity:** O(max(n, m)) — For the result list.
We simulate manual addition by processing digits from right to left, tracking the carry at each step. The loop handles different-length strings naturally by treating exhausted strings as contributing zeros.
- approach_name: Recursive Approach
is_optimal: false
code: |
def add_strings(num1: str, num2: str) -> str:
def add_helper(i: int, j: int, carry: int) -> str:
# Base case: no more digits and no carry
if i < 0 and j < 0 and carry == 0:
return ""
# Get current digits
digit1 = int(num1[i]) if i >= 0 else 0
digit2 = int(num2[j]) if j >= 0 else 0
# Calculate sum and new carry
total = digit1 + digit2 + carry
current_digit = str(total % 10)
new_carry = total // 10
# Recurse for remaining digits, then append current
return add_helper(i - 1, j - 1, new_carry) + current_digit
return add_helper(len(num1) - 1, len(num2) - 1, 0)
explanation: |
**Time Complexity:** O(max(n, m)) — Same as iterative.
**Space Complexity:** O(max(n, m)) — Call stack depth plus string concatenation.
This recursive version processes digits from right to left using the call stack. While elegant, it's less efficient due to string concatenation and recursion overhead. The iterative approach is preferred for interviews.