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:
    1. 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
    2. 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
    3. Convergence guarantee:
      • Pointers move toward each other
      • Eventually meet at the solution or exhaust all pairs

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:

  1. Start with pointers at opposite ends of a sorted array
  2. Move pointers based on comparison logic
  3. 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

  1. 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;
    
  2. Network Protocol - TCP Congestion Control
    • TCP's Fast Retransmit uses two pointers tracking:
  3. Git Merge Algorithm
    • Git's three-way merge uses pointers to traverse sorted commit histories:
  4. Redis Sorted Sets (ZRANGEBYLEX)
    • Redis uses two-pointer logic for range queries on sorted sets:
  5. 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.