// pattern debugger≡ menu

stack>core patterns / two_pointers

// Two Pointers

Two indices that converge, chase, or expand — turning O(n²) pair scans into O(n) walks on sorted or structured data.

core idea

Two indices walk the data instead of one. Because each step permanently discards a chunk of the search space — a pair, a prefix, a wall — you check what looks like O(n²) combinations in a single O(n) pass with O(1) extra space. The pattern has three shapes, and interviews use all three:

shape pointers typical trigger
opposite ends left at 0, right at n−1, converging sorted array + pair/containment question
same direction slow write, fast read chase forward in-place removal / compaction, “O(1) space”
expand from middle both start at a center, walk outward palindromes, symmetry around a point
L
1
0
3
1
5
2
7
3
R
11
4
opposite ends: L and R converge — every move discards a whole set of pairs

when to reach for it

  • The input is sorted (or sortable without losing anything you need) and the question is about a pair, triple, or range.
  • The words “in-place” or “O(1) space” appear anywhere in the problem.
  • You are comparing a sequence with itself reversed, or growing outward from a center — palindromes, mirrors, symmetry.
  • A brute force would be two nested loops where the inner loop only moves one way.

universal templates

Opposite ends — the shape you will write most often:

public int[] OppositeEnds(int[] sorted, int target)
{
    int left = 0, right = sorted.Length - 1;

    while (left < right)               // strict <: when they meet, no pair remains
    {
        int sum = sorted[left] + sorted[right];
        if (sum == target) return [left, right];

        if (sum < target) left++;      // sorted[left] is too small for *everyone* → discard it
        else right--;                  // sorted[right] is too big for *everyone* → discard it
    }
    return [];                         // no pair exists
}

Same direction (read/write) — the in-place workhorse:

public int Compact(int[] nums)
{
    int write = 0;                             // everything before write is the answer so far
    for (int read = 0; read < nums.Length; read++)
    {
        if (Keep(nums[read]))
            nums[write++] = nums[read];        // claim the slot, then advance
    }
    return write;                              // new logical length
}

Expand from middle — palindromes grow outward:

public (int Start, int Length) ExpandFromCenter(string s, int left, int right)
{
    while (left >= 0 && right < s.Length && s[left] == s[right])
    {
        left--;                        // grow the mirror outward
        right++;
    }
    return (left + 1, right - left - 1);   // we overshot by one on each side
}

Every problem below is one of these three skeletons with a different comparison in the middle.

the one question to ask

Before moving a pointer, ask: “which move lets me discard options without ever discarding the answer?” If you can’t answer that, the pattern doesn’t apply — and that’s a signal to reach for a hashmap instead.

problems

  1. 01Valid PalindromeeasyLC #125▶ interactive

    Mirror pointers from both ends, skipping non-alphanumerics.

  2. 02Two Sum II (Sorted Input)easyLC #167▶ interactive

    Opposite-end pointers; each comparison discards a whole set of pairs.

  3. Same-direction read/write pointers — the in-place compaction template.

  4. Expand from every center — the third pointer shape, on a top-five interview question.

  5. Converging pointers with a greedy proof: always move the shorter wall.

  6. 063SummediumLC #15

    Sort + outer loop + two pointers, with duplicate-skipping discipline.

  7. 07Trapping Rain WaterhardLC #42stretch

    The capstone: converging pointers carrying maxLeft/maxRight invariants.

cheat sheet — two pointers

recognize it

  • sorted array + pair/sum/closest question → opposite ends
  • "remove/keep elements in place", "O(1) space" → read/write pointers
  • palindrome or mirror comparison → both ends inward, or expand from center

key tricks

  • while (left < right) strict — meeting means nothing left to compare
  • read/write invariant: [0, write) is always the finished answer
  • 3Sum = sort + fix one element + two-pointer the rest
  • skip duplicates by sliding a pointer while nums[i] == nums[i - 1]
  • expand-from-center: try both odd (i, i) and even (i, i + 1) centers

common bugs

  • <= in the converge loop — comparing an element with itself
  • moving the wrong pointer on a tie (Container With Most Water: move the shorter wall)
  • forgetting the array must be sorted for opposite-end sums
  • returning values when the problem wants original indices — sorting destroyed them

// connections