1. Two Sum II — Input Array Is Sorted
EasyOpposite DirectionGiven a 1-indexed sorted array numbers and a target, return indices of the two numbers that add up to the target. You may not use the same element twice. The solution is guaranteed to be unique.
Example: numbers = [2, 7, 11, 15], target = 9 → [1, 2]
Think Before You Code
Before writing any code, ask yourself these questions:
- 1. What property of the input can I exploit? The array is sorted. Sorted means values increase left-to-right. This gives us a predictable relationship between index position and value.
- 2. If I pick the smallest and largest values, what does their sum tell me? If their sum is too big, the largest value is too large for ANY remaining element to pair with — we can eliminate it entirely. If too small, the smallest is too small for any remaining element.
- 3. Can I avoid checking every pair? Yes. Each comparison eliminates an entire row or column of the pair matrix. Instead of n² checks, we need at most n.
Brute Force: O(n²)
The obvious approach: for each element, scan all elements after it to find a matching pair. This works but completely ignores the sorted property. For an array of 10,000 elements, you would check ~50 million pairs when the answer could be found in at most 10,000 steps.
The “Aha” Moment: From Brute Force to Optimal
Here is the insight: in the brute force, the inner loop exists because after picking arr[i], we do not know where the matching element is. But in a sorted array, we do know something: if we pick the smallest and largest elements and their sum is too big, then the largest element is too big to pair with any element (since the smallest is already as small as possible). We can safely discard it. This insight — one comparison eliminates one candidate permanently — is what collapses the inner loop into a single pointer movement.
Why Two Pointers Instead of a Hash Map?
1def twoSum(numbers, target):2 left, right = 0, len(numbers) - 1 # Start at opposite ends of sorted array34 while left < right: # WHY <? We need two DISTINCT elements for a valid pair5 s = numbers[left] + numbers[right]6 if s == target:7 return [left + 1, right + 1] # Convert to 1-indexed as required8 elif s < target:9 left += 1 # WHY? Sum too small — only way to increase it in a10 # sorted array is to move left pointer right11 else:12 right -= 1 # WHY? Sum too large — only way to decrease it is to13 # move right pointer left1415 return [] # Problem guarantees a solution exists, but defensive coding16# Time: O(n) | Space: O(1)Dry Run: numbers = [2, 7, 11, 15], target = 9
| Step | left | right | sum | Action |
|---|---|---|---|---|
| 1 | 0 (val=2) | 3 (val=15) | 17 | 17 > 9 → move right left |
| 2 | 0 (val=2) | 2 (val=11) | 13 | 13 > 9 → move right left |
| 3 | 0 (val=2) | 1 (val=7) | 9 | Found! Return [1, 2] |
Key Line Spotlight
elif s < target: left += 1This single line is why the algorithm works. Because the array is sorted, incrementing left is guaranteed to increase the sum (or keep it the same if duplicates exist). Without the sorted property, moving left could increase or decrease the sum unpredictably, and the algorithm would fail.
Why O(n) Time, O(1) Space?
- Time O(n): Each iteration moves at least one pointer inward. Left can move at most n-1 times right, and right can move at most n-1 times left. Total moves ≤ n-1, so the while loop runs at most n-1 times.
- Space O(1): We only use two integer variables (left, right) regardless of input size. No hash map, no extra array.
Common Mistakes
- Forgetting 1-indexed output: The problem says 1-indexed but internally you use 0-indexed. Return
[left+1, right+1], not[left, right]. - Using <= instead of <: With
left <= right, you can pair an element with itself. The problem says “may not use the same element twice.” - Not checking if input is sorted: This algorithm only works on sorted arrays. If the interviewer gives you an unsorted array, sort it first (but the indices change) or use a hash map.
Time: O(n) — Space: O(1)