189 lines
8.1 KiB
YAML
189 lines
8.1 KiB
YAML
title: Sum of Two Integers
|
|
slug: sum-of-two-integers
|
|
difficulty: medium
|
|
leetcode_id: 371
|
|
leetcode_url: https://leetcode.com/problems/sum-of-two-integers/
|
|
categories:
|
|
- math
|
|
patterns:
|
|
- slug: bit-manipulation
|
|
is_optimal: true
|
|
|
|
function_signature: "def get_sum(a: int, b: int) -> int:"
|
|
|
|
test_cases:
|
|
visible:
|
|
- input: { a: 1, b: 2 }
|
|
expected: 3
|
|
- input: { a: 2, b: 3 }
|
|
expected: 5
|
|
hidden:
|
|
- input: { a: -1, b: 1 }
|
|
expected: 0
|
|
- input: { a: 0, b: 0 }
|
|
expected: 0
|
|
- input: { a: -12, b: -8 }
|
|
expected: -20
|
|
- input: { a: 100, b: -100 }
|
|
expected: 0
|
|
- input: { a: 1000, b: -1 }
|
|
expected: 999
|
|
- input: { a: -1000, b: 1000 }
|
|
expected: 0
|
|
|
|
description: |
|
|
Given two integers `a` and `b`, return *the sum of the two integers without using the operators* `+` *and* `-`.
|
|
|
|
constraints: |
|
|
- `-1000 <= a, b <= 1000`
|
|
|
|
examples:
|
|
- input: "a = 1, b = 2"
|
|
output: "3"
|
|
explanation: "1 + 2 = 3"
|
|
- input: "a = 2, b = 3"
|
|
output: "5"
|
|
explanation: "2 + 3 = 5"
|
|
|
|
explanation:
|
|
intuition: |
|
|
Think back to how you learned to add numbers by hand in primary school: you add each column, and if the result is 10 or more, you "carry" to the next column. Binary addition works the same way, but simpler since each digit is either 0 or 1.
|
|
|
|
The key insight is that we can decompose addition into two separate operations:
|
|
|
|
1. **Sum without carrying**: When you add two bits, ignoring any carry, the result follows the XOR pattern: `0+0=0`, `0+1=1`, `1+0=1`, `1+1=0` (the "1+1=0" is because we're ignoring the carry).
|
|
|
|
2. **The carry itself**: A carry occurs only when both bits are 1. This is exactly the AND operation, but the carry needs to be added to the *next* position, so we shift it left by one.
|
|
|
|
By repeatedly computing the "sum without carry" (XOR) and the "carry" (AND shifted left), we eventually get to a point where there's no more carry, and we have our answer.
|
|
|
|
Think of it like this: each iteration handles one "wave" of carries. For most numbers, this converges very quickly since carries can only propagate as far as the number of bits.
|
|
|
|
approach: |
|
|
We solve this using **Bit Manipulation** to simulate binary addition:
|
|
|
|
**Step 1: Understand the two key operations**
|
|
|
|
- `a XOR b`: Gives the sum of bits where at most one of them is 1 (no carry needed)
|
|
- `(a AND b) << 1`: Identifies positions where both bits are 1 (carry occurs) and shifts the carry to the next position
|
|
|
|
|
|
|
|
**Step 2: Iterate until no carry remains**
|
|
|
|
- Compute `sum_without_carry = a XOR b`
|
|
- Compute `carry = (a AND b) << 1`
|
|
- Set `a = sum_without_carry` and `b = carry`
|
|
- Repeat while `b != 0` (while there's still a carry to process)
|
|
|
|
|
|
|
|
**Step 3: Handle negative numbers (Python-specific)**
|
|
|
|
- Python integers have arbitrary precision, so negative numbers don't naturally overflow
|
|
- We use a 32-bit mask (`0xFFFFFFFF`) to simulate 32-bit integer arithmetic
|
|
- If the result has its sign bit set (bit 31), we convert it back to a negative Python integer
|
|
|
|
|
|
|
|
**Step 4: Return the result**
|
|
|
|
- When `b` becomes 0, all carries have been processed
|
|
- Return `a` which now contains the final sum
|
|
|
|
common_pitfalls:
|
|
- title: Infinite Loop with Negative Numbers
|
|
description: |
|
|
In Python, integers have unlimited precision. When working with negative numbers, the carry can keep propagating indefinitely because Python doesn't have fixed-width integers that naturally overflow.
|
|
|
|
For example, adding `-1` and `1`: in two's complement 32-bit representation, `-1` is all 1s (`0xFFFFFFFF`). Without masking, the carry keeps growing beyond 32 bits, and the loop never terminates.
|
|
|
|
The fix is to mask all operations to 32 bits using `& 0xFFFFFFFF`.
|
|
wrong_approach: "Direct XOR and AND without bit masking"
|
|
correct_approach: "Mask to 32 bits and handle sign conversion at the end"
|
|
|
|
- title: Forgetting to Convert Back to Signed
|
|
description: |
|
|
When you mask to 32 bits in Python, you get an unsigned result. If the actual sum is negative, you'll have a large positive number instead.
|
|
|
|
For example, `-1 + 0` should return `-1`, but with 32-bit masking you get `4294967295` (`0xFFFFFFFF`). You need to check if the sign bit (bit 31) is set and convert back to a negative number.
|
|
wrong_approach: "Return masked result directly"
|
|
correct_approach: "Check sign bit and convert: if result > 0x7FFFFFFF, subtract 0x100000000"
|
|
|
|
- title: Using + or - Operators
|
|
description: |
|
|
The entire point of this problem is to implement addition without using `+` or `-`. Some solutions accidentally use these operators in loop increments or other places.
|
|
|
|
Be careful that all arithmetic is done purely with bit operations: XOR, AND, OR, NOT, and shifts.
|
|
wrong_approach: "Using i += 1 in a loop or any arithmetic operators"
|
|
correct_approach: "Use only bitwise operations throughout"
|
|
|
|
key_takeaways:
|
|
- "**XOR for addition without carry**: `a ^ b` gives you the sum of each bit position where there's no carry"
|
|
- "**AND + shift for carry**: `(a & b) << 1` computes and positions the carry for the next iteration"
|
|
- "**Two's complement awareness**: When implementing bit manipulation in Python, remember to handle the difference between Python's arbitrary-precision integers and fixed-width machine integers"
|
|
- "**Foundation for hardware**: This is exactly how addition circuits work in CPUs - a series of half-adders and full-adders propagating carries"
|
|
|
|
time_complexity: "O(1). The number of iterations is bounded by the number of bits (at most 32 for 32-bit integers), which is constant."
|
|
space_complexity: "O(1). We only use a few variables regardless of input size."
|
|
|
|
solutions:
|
|
- approach_name: Bit Manipulation (Iterative)
|
|
is_optimal: true
|
|
code: |
|
|
def get_sum(a: int, b: int) -> int:
|
|
# 32-bit mask to handle negative numbers in Python
|
|
MASK = 0xFFFFFFFF
|
|
# Maximum positive value in 32-bit signed integer
|
|
MAX_INT = 0x7FFFFFFF
|
|
|
|
while b != 0:
|
|
# XOR gives sum without considering carry
|
|
sum_without_carry = (a ^ b) & MASK
|
|
# AND finds where both bits are 1 (carry positions)
|
|
# Left shift moves carry to next bit position
|
|
carry = ((a & b) << 1) & MASK
|
|
|
|
# Prepare for next iteration
|
|
a = sum_without_carry
|
|
b = carry
|
|
|
|
# If a is negative in 32-bit two's complement, convert to Python negative
|
|
# Numbers > MAX_INT have their sign bit set (are negative)
|
|
if a > MAX_INT:
|
|
# Convert from unsigned 32-bit to signed Python int
|
|
a = ~(a ^ MASK)
|
|
|
|
return a
|
|
explanation: |
|
|
**Time Complexity:** O(1) - At most 32 iterations for 32-bit integers.
|
|
|
|
**Space Complexity:** O(1) - Only a constant number of variables used.
|
|
|
|
We simulate binary addition by separating the sum into "sum without carry" (XOR) and "carry" (AND shifted left). We repeat until there's no carry. The masking handles Python's arbitrary-precision integers, and we convert back to a signed integer at the end if needed.
|
|
|
|
- approach_name: Recursive Bit Manipulation
|
|
is_optimal: false
|
|
code: |
|
|
def get_sum(a: int, b: int) -> int:
|
|
MASK = 0xFFFFFFFF
|
|
MAX_INT = 0x7FFFFFFF
|
|
|
|
# Mask inputs to 32 bits
|
|
a = a & MASK
|
|
b = b & MASK
|
|
|
|
# Base case: no carry to add
|
|
if b == 0:
|
|
# Convert back to signed if needed
|
|
return a if a <= MAX_INT else ~(a ^ MASK)
|
|
|
|
# Recursive case: add the carry
|
|
return get_sum(a ^ b, (a & b) << 1)
|
|
explanation: |
|
|
**Time Complexity:** O(1) - At most 32 recursive calls.
|
|
|
|
**Space Complexity:** O(1) - Call stack is bounded by 32 levels, which is constant.
|
|
|
|
This is the same algorithm expressed recursively. The base case is when there's no carry (`b == 0`), and each recursive call processes one round of carry propagation. While elegant, the iterative version is preferred in practice to avoid stack overhead.
|