Example 1
ready- Input
numbers = [ 2, 7, 11, 15 ] target = 9- Expected
[ 1, 2 ]- Why
- 2 + 7 = 9 → positions 1 and 2 (1-indexed)
This is Two Sum with one crucial upgrade: the array is already sorted in non-decreasing order — and that changes everything about the right solution.
Given a 1-indexed array numbers sorted in non-decreasing order and an integer
target, find the two positions whose values add up to target. Return them as
[i, j] with i < j, using 1-based indexing.
2 <= numbers.length <= 100_000-1_000_000_000 <= numbers[i], target <= 1_000_000_000numbers is sorted in non-decreasing order.Hints
In unsorted Two Sum you paid O(n) memory for a hash map. Sorted order is information — it should be able to replace that memory. How does the sum change when you swap in a bigger number? A smaller one?
Point left at the smallest value and right at the largest. Their sum is either
right, too small, or too big. Two of those three cases tell you a pointer that can
be moved — and why it's safe to move it.
If numbers[left] + numbers[right] > target, then numbers[right] is too big to
pair with ANY value at or right of left — every candidate partner is ≥
numbers[left]. So right can retire inward without losing the answer.
The exact same converging movement solves Container With Most Water and 3Sum. Once the "which pointer moves, and what makes that safe" argument clicks here, those two are half-done.
Visible cases
numbers = [
2,
7,
11,
15
]
target = 9[
1,
2
]numbers = [
2,
3,
4
]
target = 6[
1,
3
]numbers = [
-1,
0
]
target = -1[
1,
2
]Interview signal
Every pair, same as unsorted Two Sum:
def two_sum_sorted(numbers: list[int], target: int) -> list[int]:
n = len(numbers)
for i in range(n - 1):
for j in range(i + 1, n):
if numbers[i] + numbers[j] == target:
return [i + 1, j + 1]
return []O(n²) time — and it never once used the fact that the input is sorted. When a problem hands you structure and your solution doesn't touch it, that's the smell to chase.
(A hash map also still works in O(n) time / O(n) space — but the follow-up asks us to do better on space.)
Put left on the smallest element, right on the largest, and reason about the sum:
sum == target → done.sum > target → the current numbers[right] is too big for every remaining
partner (they're all ≥ numbers[left]), so it can never be in the answer: right -= 1.sum < target → symmetric: numbers[left] is too small for every remaining partner:
left += 1.def two_sum_sorted(numbers: list[int], target: int) -> list[int]:
left, right = 0, len(numbers) - 1
while left < right:
s = numbers[left] + numbers[right]
if s == target:
return [left + 1, right + 1]
if s > target:
right -= 1
else:
left += 1
return []Each step permanently retires one element with a proof it can't participate. n elements, so at most n steps: O(n) time, O(1) space.
The takeaway to keep: sorted order is a resource. Here it replaced an entire hash map — the pointers extract the same "have I seen your partner?" information from position instead of memory.
Ready to run.
3 cases are queued.
Visible testcase
Case 1
Expected
[ 1, 2 ]