NEETCODE - Two Pointers: Two Integer Sum II
From O(n²) brute force to O(n) two-pointer technique
Technology: Python, Algorithms
Skill: Two Pointers, O(n)
Problem Statement
Given an array of integers numbers that is sorted in non-decreasing order, find two numbers that add up to a specific target number.
Return the indices of the two numbers (1-indexed) as [index1, index2] where index1 < index2.
Key Constraints:
- The array is already sorted
- Exactly one solution exists
- Must use O(1) additional space
- Cannot use the same element twice
- Return 1-indexed positions (not 0-indexed)
Example:
Input: numbers = [1,2,3,4], target = 3
Output: [1,2]
Explanation:
1 + 2 = 3
index1 = 1, index2 = 2 (1-indexed)
Constraints:
2 <= numbers.length <= 1000
-1000 <= numbers[i] <= 1000
-1000 <= target <= 1000
Solutions Progression
Approach 1: Brute Force (Nested Loops)
def twoSum(numbers: List[int], target: int) -> List[int]:
n = len(numbers)
for i in range(n):
for j in range(i + 1, n):
if numbers[i] + numbers[j] == target:
return [i + 1, j + 1] # 1-indexed
return []
Time Complexity: O(n²)
- Outer loop runs n times
- Inner loop runs (n-1), (n-2), ..., 1 times
- Total comparisons: (n-1) + (n-2) + ... + 1 = n(n-1)/2 ≈ O(n²)
Space Complexity: O(1)
- Only using two loop variables, no additional data structures
Why this approach works:
- Checks every possible pair exhaustively
- Guaranteed to find the solution if it exists
Trade-offs:
- ✅ Simple to understand and implement
- ✅ O(1) space meets constraint
- ❌ Ignores the sorted property completely
- ❌ Wastes time checking impossible pairs
Approach 2: Hash Map (Complement Lookup)
def twoSum(numbers: List[int], target: int) -> List[int]:
seen = {} # value -> index
for i, num in enumerate(numbers):
complement = target - num
if complement in seen:
return [seen[complement] + 1, i + 1] # 1-indexed
seen[num] = i
return []
Time Complexity: O(n)
- Single pass through array: O(n)
- Hash lookups are O(1) average case
- Total: O(n)
Space Complexity: O(n)
- Worst case: store all n elements in hash map
Why this approach works:
- For each number, check if its complement exists
- Store numbers we've seen for instant lookup
Trade-offs:
- ✅ Optimal time complexity
- ✅ Works on unsorted arrays too
- ❌ Violates the O(1) space constraint
- ❌ Doesn't leverage the sorted property
Approach 3: Two Pointers (Optimal) ⭐
def twoSum(numbers: List[int], target: int) -> List[int]:
left = 0
right = len(numbers) - 1
while left < right:
current_sum = numbers[left] + numbers[right]
if current_sum == target:
return [left + 1, right + 1] # 1-indexed
elif current_sum < target:
left += 1 # Need larger sum
else:
right -= 1 # Need smaller sum
return []
Time Complexity: O(n) Detailed breakdown:
- Each iteration moves one pointer
- Left pointer moves from 0 → n-1 (worst case)
- Right pointer moves from n-1 → 0 (worst case)
- Total pointer movements: At most n steps
- Each step does O(1) work (addition, comparison)
- Result: O(n)
Mathematical justification:
- At each step, we eliminate at least one index from consideration
- Start with n possible positions, end with 0
- Maximum iterations = n
Space Complexity: O(1)
- Only two pointer variables (left, right)
- No additional data structures
Why this approach works:
- The key insight is the sorted property:
- If sum is too small:
numbers[left]must be too small- All pairs with
numbers[left]will be too small - Move left pointer right to get larger values
- All pairs with
- If sum is too large:
numbers[right]must be too large- All pairs with
numbers[right]will be too large - Move right pointer left to get smaller values
- All pairs with
- Convergence guarantee:
- Pointers move toward each other
- Eventually meet at the solution or exhaust all pairs
- If sum is too small:
Trade-offs:
- ✅ Optimal time: O(n)
- ✅ Optimal space: O(1)
- ✅ Leverages sorted property perfectly
- ✅ Elegant and intuitive
- ❌ Only works on sorted arrays
Pattern Recognition
Core Algorithmic Pattern: Two Pointers (Opposite Ends)
This is a fundamental pattern where:
- Start with pointers at opposite ends of a sorted array
- Move pointers based on comparison logic
- Converge toward the solution
Key Insight That Unlocks the Problem
The sorted property + two pointers = O(n) without extra space Most people's first instinct is hash map (from regular Two Sum). The breakthrough is recognizing:
- "Sorted" means we can make directional decisions
- If sum is wrong, we know exactly which pointer to move
- No need to remember anything → O(1) space
Bridge to Real Systems
Where This Algorithm Lives in Production
- Database Query Optimization
- PostgreSQL's merge join uses this exact pattern:
-- When joining two sorted tables SELECT * FROM table1 JOIN table2 ON table1.id = table2.id ORDER BY table1.id; - Network Protocol - TCP Congestion Control
- TCP's Fast Retransmit uses two pointers tracking:
- Git Merge Algorithm
- Git's three-way merge uses pointers to traverse sorted commit histories:
- Redis Sorted Sets (ZRANGEBYLEX)
- Redis uses two-pointer logic for range queries on sorted sets:
- Google's MapReduce - Merge Phase
- When combining sorted outputs from reducers:
Complexity Summary Table
| Approach | Time | Space | Pros | Cons | Use Case |
|---|---|---|---|---|---|
| Brute Force | O(n²) | O(1) | Simple, no preprocessing | Inefficient, ignores sorted property | Teaching, n < 20 |
| Hash Map | O(n) | O(n) | Fast, works on unsorted | Uses extra space, violates constraint | Unsorted arrays |
| Two Pointers ⭐ | O(n) | O(1) | Optimal time & space, elegant | Requires sorted array | Production solution |
| Binary Search | O(n log n) | O(1) | Alternative approach | Slower than two pointers | Academic interest |
Winner: Two Pointers - O(n) time, O(1) space, leverages sorted property perfectly.