Home / Coding / Two Pointer

Two Pointer

Instead of checking every pair with nested loops, two pointers let you scan smarter by moving two indices through the array at the same time.

  • Replaces O(n²) brute force with O(n) by using sorted order or array structure
  • Two modes:
    1. Converging — start at both ends, move toward each other (pairs, palindromes, containers)
    2. Fast and slow — both start at same end, fast scouts ahead (filter, partition, remove)
  • Reach for it when finding pairs with a target sum, filtering in place, or partitioning.

Practice Tips

How to Identify a Two Pointer Problem

  • Input is a sorted array or can benefit from sorting
  • You need to find pairs/triplets satisfying a condition
  • You need to do in-place modifications (remove, partition, reorder)
  • Problem involves comparing elements from both ends

Two Pointer vs. Other Techniques

If you see... Use... Not...
Sorted array + find pair with sum Two Pointer (opposite) HashMap (same result, worse space)
Subarray with condition (variable size) Sliding Window (02_sliding_window.md) Two Pointer
In-place filter/remove Two Pointer (same direction) Extra array
Linked list cycle/middle Fast & Slow (05_fast_slow_linkedlist.md) Two Pointer on array

Interview Approach

  1. Ask: Is the array sorted? Can I sort it?
  2. Decide: Opposite direction or same direction?
  3. Identify: What condition moves which pointer?
  4. Watch for: Duplicates, off-by-one, edge cases (empty, single element)

Two Main Patterns

Pattern 1: Opposite Direction (Most Common)

// Pointers start at opposite ends, move toward each other
int left = 0;
int right = array.length - 1;

while (left < right) {
    // Process current pair
    if (condition) {
        left++;
    } else {
        right--;
    }
}

Pattern 2: Same Direction (Fast & Slow)

// Both pointers start at same end, move at different speeds
int slow = 0;
int fast = 0;

while (fast < array.length) {
    // Process with fast pointer
    if (condition) {
        array[slow] = array[fast];
        slow++;
    }
    fast++;
}

Quick Revision - Must Do Problems

If short on time, solve only these. They cover ~90% of two pointer patterns.

# Problem Pattern Why It's Essential Time to Solve
3 Valid Palindrome [B75-125] Opposite Classic two-pointer validation — most common interview opener 10-15 min
7 Container With Most Water [B75-11] Opposite Greedy shrink teaches the core two-pointer intuition 15-20 min
8 3Sum [B75-15] Opposite + Loop Sorting, duplicate skipping, nested two-pointer 25-30 min
9 Sort Colors Three Pointers Unique three-pointer (Dutch National Flag) 20-25 min
11 Trapping Rain Water Opposite Hardest pattern, most interview-asked 30-40 min

Problem 1: Reverse String

Difficulty Time to Solve Pattern
Easy 5-10 min Opposite Direction

Reverse a string in-place.

Example:

Input:  s = ['h', 'e', 'l', 'l', 'o']
Output: ['o', 'l', 'l', 'e', 'h']

Solution:

public void reverseString(char[] s) {
    // 1. Initialize pointers at opposite ends
    int left = 0;
    int right = s.length - 1;

    // 2. Swap and move toward center
    while (left < right) {
        char temp = s[left];
        s[left] = s[right];
        s[right] = temp;

        left++;
        right--;
    }
}

Complexity:

Time O(n) — single pass
Space O(1) — in-place

Real World: In-place string reversal is used in text editors for undo buffers, in DNA sequence tools to produce the reverse strand, and in network packet processing to reverse byte order without allocating new memory.

Variations:

  1. Reverse only vowels — two pointers skip consonants inward until both land on vowels, then swap. Same converging structure, different skip condition.
  2. Reverse words in a sentence — reverse the entire string first, then reverse each individual word in place. Two applications of this same pattern.

Interview Disguises:

  • "Given a DNA strand stored as a char array, reverse it in place to produce the reverse strand for complement analysis." — same swap-from-both-ends logic, different domain.
  • "A text editor stores the current line as a char array. Implement an in-place reversal for the 'flip line' feature." — reverse string verbatim; the char array framing is identical.

Problem 2: Two Sum II (Sorted Array)

Difficulty Time to Solve Pattern
Easy 10-15 min Opposite Direction

Given a 1-indexed sorted array, find two numbers that add up to target. Return their indices.

Example:

Input:  numbers = [2, 7, 11, 15], target = 9
Output: [1, 2]  (1-based indices)

Solution:

public int[] twoSum(int[] numbers, int target) {
    // 1. Initialize pointers at opposite ends
    int left = 0;
    int right = numbers.length - 1;

    // 2. Move pointers toward each other
    while (left < right) {
        int sum = numbers[left] + numbers[right];

        if (sum == target) {
            return new int[]{left + 1, right + 1};  // 1-based index
        } else if (sum < target) {
            left++;   // Need larger sum
        } else {
            right--;  // Need smaller sum
        }
    }

    return new int[]{-1, -1};  // Not found
}

Complexity:

Time O(n) — single pass with two pointers
Space O(1) — only two pointers

Two Sum [B75-1] — unsorted array: The Blind 75 Two Sum is the same complement idea on an unsorted array. The array can't be sorted without losing original indices, so use a HashMap instead of two pointers:

public int[] twoSum(int[] nums, int target) {
    Map<Integer, Integer> map = new HashMap<>();
    for (int i = 0; i < nums.length; i++) {
        int complement = target - nums[i];
        if (map.containsKey(complement)) {
            return new int[]{map.get(complement), i};
        }
        map.put(nums[i], i);
    }
    return new int[]{};
}

Real World: Finding two products in a sorted price list whose combined cost hits a gift card value exactly. Also used in financial systems to pair offsetting transactions in a sorted ledger, and in signal processing to find two frequency components that sum to a target.

Variations:

  1. Count all pairs — instead of returning the first pair, count every pair that sums to target. After a match, skip duplicates on both sides and continue (don't return early).
  2. Two Sum on unsorted array — sorting would lose original indices, so use a HashMap: store each value's index as you scan; check if complement exists. O(n) time, O(n) space.

Interview Disguises:

  • "Given a sorted list of product prices, find two items whose combined price exactly matches a gift card balance." — sorted array + pair summing to target = two sum II.
  • "A sorted log of server response times — find two entries that together equal a target latency budget for an SLA report." — same converging two-pointer on a sorted array.

Problem 3: Valid Palindrome ⭐ MUST DO [B75-125]

Difficulty Time to Solve Pattern
Easy 10-15 min Opposite Direction

Check if a string is a palindrome, considering only alphanumeric characters and ignoring case.

Example:

Input:  s = "A man, a plan, a canal: Panama"
Output: true

Input:  s = "race a car"
Output: false

Solution:

public boolean isPalindrome(String s) {
    // 1. Initialize pointers at opposite ends
    int left = 0;
    int right = s.length() - 1;

    // 2. Move pointers toward each other
    while (left < right) {
        // Skip non-alphanumeric characters
        while (left < right && !Character.isLetterOrDigit(s.charAt(left))) {
            left++;
        }
        while (left < right && !Character.isLetterOrDigit(s.charAt(right))) {
            right--;
        }

        // Compare characters (case-insensitive)
        if (Character.toLowerCase(s.charAt(left)) != Character.toLowerCase(s.charAt(right))) {
            return false;
        }

        left++;
        right--;
    }

    return true;
}

Complexity:

Time O(n) — single pass
Space O(1) — constant extra space

Real World: Validating palindromic usernames or passphrases after stripping punctuation. In bioinformatics, detecting palindromic DNA sequences (restriction enzyme recognition sites) while ignoring non-base characters in raw sequence data.

Variations:

  1. Valid Palindrome II (LC 680) — you may delete at most one character; return true if the result is a palindrome. On mismatch, try skipping left or skipping right and check if either remaining window is a palindrome.
  2. Longest palindromic substring — instead of checking the whole string, expand two pointers outward from each center (both odd and even centers). Different goal, same outward-expand intuition.

Interview Disguises:

  • "A security system requires passphrases that read the same forwards and backwards after stripping spaces and special characters. Validate a user's input." — isPalindrome with alphanumeric filtering; exact same code.
  • "Given a raw DNA sequence string that may contain gap characters (-), check if the base sequence is palindromic." — skip non-letter characters with the inner while loops; structurally identical.

Problem 4: Remove Duplicates from Sorted Array

Difficulty Time to Solve Pattern
Easy 10-15 min Same Direction (Fast & Slow)

Remove duplicates in-place from a sorted array and return new length.

Example:

Input:  nums = [1, 1, 2]
Output: 2, nums = [1, 2, _]

Input:  nums = [0, 0, 1, 1, 1, 2, 2, 3, 3, 4]
Output: 5, nums = [0, 1, 2, 3, 4, _, _, _, _, _]

Solution:

public int removeDuplicates(int[] nums) {
    if (nums.length == 0) {
        return 0;
    }

    // 1. Slow pointer marks last unique position
    int slow = 0;

    // 2. Fast pointer explores array (starts at 1 since first element is always unique)
    for (int fast = 1; fast < nums.length; fast++) {
        // Found new unique element
        if (nums[fast] != nums[slow]) {
            slow++;
            nums[slow] = nums[fast];
        }
    }

    return slow + 1;  // Length is slow + 1
}

Complexity:

Time O(n) — single pass
Space O(1) — in-place

Real World: Deduplicating sorted log entries in place before writing to disk — sensors often produce repeated readings and storing every duplicate wastes storage. Also used in database engines to compact sorted index pages without allocating extra memory.

Variations:

  1. Allow at most 2 duplicates (LC 80) — change the condition to nums[fast] != nums[slow - 1] (compare two positions back instead of one). The slow pointer now allows up to two of each value through.
  2. Remove duplicates from unsorted array — two pointers no longer work without sorting first. Use a HashSet to track seen values; O(n) space trade-off.

Interview Disguises:

  • "A temperature sensor logs sorted readings to a fixed-size buffer. Remove repeated values in place so the buffer stores only unique readings before archiving." — remove duplicates from sorted array; same fast/slow overwrite.
  • "A sorted contact list has duplicate phone numbers. Deduplicate it in place without allocating a second list." — identical problem, contact list framing.

Problem 5: Move Zeroes

Difficulty Time to Solve Pattern
Easy 10-15 min Same Direction (Fast & Slow)

Move all zeros to the end while maintaining relative order of non-zero elements.

Example:

Input:  nums = [0, 1, 0, 3, 12]
Output: [1, 3, 12, 0, 0]

Solution:

public void moveZeroes(int[] nums) {
    // 1. Slow pointer marks write position for non-zero
    int slow = 0;

    // 2. Fast pointer explores array
    for (int fast = 0; fast < nums.length; fast++) {
        if (nums[fast] != 0) {
            // Swap to maintain all elements (zeros drift right)
            int temp = nums[slow];
            nums[slow] = nums[fast];
            nums[fast] = temp;
            slow++;
        }
    }
}

Complexity:

Time O(n) — single pass
Space O(1) — in-place

Real World: Memory compaction in garbage collectors — live objects (non-zero) are compacted to one end while free slots (zeros) are pushed to the other, preserving the relative order of live data. Also used in sparse array compression before transmission.

Variations:

  1. Move all negatives to the front — same fast/slow swap pattern, change the condition from != 0 to < 0. Relative order of negatives and non-negatives is preserved.
  2. Move all instances of value k to the end — generalize the zero condition to != k. This is the Remove Element problem but preserving order via swap instead of overwrite.

Interview Disguises:

  • "A memory allocator represents pages as an array. Move all free pages (0) to the end in a single pass while keeping allocated pages in their original order." — move zeroes exactly; swap not overwrite to preserve order.
  • "A music playlist has deleted tracks marked as 0. Compact the playlist so all active tracks come first, in their original order, with deleted slots at the end." — same swap-based filter pattern.

Problem 6: Squares of a Sorted Array

Difficulty Time to Solve Pattern
Easy 10-15 min Opposite Direction

Given a sorted array (may contain negatives), return squares in sorted order.

Example:

Input:  nums = [-4, -1, 0, 3, 10]
Output: [0, 1, 9, 16, 100]

Input:  nums = [-7, -3, 2, 3, 11]
Output: [4, 9, 9, 49, 121]

Solution:

public int[] sortedSquares(int[] nums) {
    int n = nums.length;
    int[] result = new int[n];

    // 1. Initialize pointers at opposite ends
    int left = 0;
    int right = n - 1;

    // 2. Fill result from end (largest absolute values are at the edges)
    for (int i = n - 1; i >= 0; i--) {
        int leftSquare = nums[left] * nums[left];
        int rightSquare = nums[right] * nums[right];

        if (leftSquare > rightSquare) {
            result[i] = leftSquare;
            left++;
        } else {
            result[i] = rightSquare;
            right--;
        }
    }

    return result;
}

Complexity:

Time O(n) — single pass
Space O(n) — result array

Real World: Computing squared deviations from a reference point for a sorted list of sensor readings (some above, some below baseline) — used in statistics for variance calculations. Also appears in computational geometry when sorting points by squared distance.

Variations:

  1. Merge two sorted arrays — same fill-from-end idea with two pointers, one per array, picking the larger element at each step. The direction flips but the two-pointer merge logic is the same.
  2. Sort by absolute value — instead of squaring, compare Math.abs(nums[left]) vs Math.abs(nums[right]) and place into result from the end. No multiplication needed.

Interview Disguises:

  • "Given a sorted list of temperature deviations from 0°C (some negative, some positive), return their squared values in ascending order for a variance report." — squares of sorted array; negatives make the largest squares appear at both ends.
  • "Account balances are sorted (debits negative, credits positive). Return squared balances in ascending order to compute a volatility metric." — same: sorted array with negatives, square and sort = fill result from end.

Problem 7: Container With Most Water ⭐ MUST DO [B75-11]

Difficulty Time to Solve Pattern
Medium 15-20 min Opposite Direction

Find two lines that together with x-axis form a container holding the most water.

Example:

Input:  height = [1, 8, 6, 2, 5, 4, 8, 3, 7]
Output: 49
         (between index 1 (height=8) and index 8 (height=7): width=7, height=7, area=49)

Why Greedy Works:

  • Area = width x min(left_height, right_height)
  • Moving pointers inward always decreases width by 1
  • If we move the taller pointer, min height can only stay the same or decrease → area guaranteed to decrease
  • If we move the shorter pointer, min height might increase → area might increase
  • Therefore, always move the shorter pointer — it's the only move that has a chance of finding a larger area

Solution:

public int maxArea(int[] height) {
    int left = 0;
    int right = height.length - 1;
    int maxArea = 0;

    while (left < right) {
        int width = right - left;
        int minHeight = Math.min(height[left], height[right]);
        maxArea = Math.max(maxArea, width * minHeight);

        // Move the shorter line — only way area could increase
        if (height[left] < height[right]) {
            left++;
        } else {
            right--;
        }
    }

    return maxArea;
}

Complexity:

Time O(n) — single pass
Space O(1) — constant extra space

Real World: Optimal placement of two retaining walls across a terrain profile to maximize reservoir capacity. Also used in terrain analysis GIS tools and in display layout engines to find two UI panels that maximize the shared space between them.

Variations:

  1. Largest rectangle in histogram — related "area = width × height" framing but requires a monotonic stack, not two pointers. Worth knowing the connection: both problems maximize a rectangle, but the constraint is different.
  2. 3-container variant — find three lines that maximize total enclosed area. No clean two-pointer solution; requires different approach. Useful to recognize when the greedy argument breaks down.

Interview Disguises:

  • "You're siting two dam walls across a valley. Given terrain heights at each position, find the two positions that trap the most water between them." — container with most water verbatim; same greedy: always move the shorter wall.
  • "A city planner has a row of buildings of varying heights. Find two buildings that maximize the usable open-air plaza between them (area = distance × shorter building)." — same formula and greedy logic; building framing instead of water.

Problem 8: 3Sum ⭐ MUST DO [B75-15]

Difficulty Time to Solve Pattern
Medium 25-30 min Opposite Direction + Loop

Find all unique triplets that sum to zero.

Example:

Input:  nums = [-1, 0, 1, 2, -1, -4]
Output: [[-1, -1, 2], [-1, 0, 1]]

Input:  nums = [0, 1, 1]
Output: []

Input:  nums = [0, 0, 0]
Output: [[0, 0, 0]]

Key Insight: Sort the array, then for each element, use two pointers to find pairs that sum to the negative of that element. Skip duplicates at every level to avoid duplicate triplets.

Solution:

public List<List<Integer>> threeSum(int[] nums) {
    List<List<Integer>> result = new ArrayList<>();
    Arrays.sort(nums);

    for (int i = 0; i < nums.length - 2; i++) {
        // Skip duplicates for first number
        if (i > 0 && nums[i] == nums[i - 1]) {
            continue;
        }

        int left = i + 1;
        int right = nums.length - 1;
        int target = -nums[i];

        while (left < right) {
            int sum = nums[left] + nums[right];

            if (sum == target) {
                result.add(Arrays.asList(nums[i], nums[left], nums[right]));

                // Skip duplicates for second and third numbers
                while (left < right && nums[left] == nums[left + 1]) {
                    left++;
                }
                while (left < right && nums[right] == nums[right - 1]) {
                    right--;
                }

                // Move both: we found a match, so moving only one side
                // would make sum != target (since we skipped duplicates)
                left++;
                right--;
            } else if (sum < target) {
                left++;
            } else {
                right--;
            }
        }
    }

    return result;
}

Complexity:

Time O(n^2) — sort O(n log n) + nested two-pointer O(n^2)
Space O(1) — ignoring output space (sort is in-place)

Real World: Fraud detection: finding three transactions in a sorted ledger that net to exactly zero — a signature of triangular money laundering (money goes out through one path and comes back through two others). Also used in physics simulations to find three force vectors that cancel.

Variations:

  1. 3Sum Closest (LC 16) — find the triplet whose sum is closest to a target (not necessarily zero). Same sort + two-pointer loop; track minDiff = Math.abs(sum - target) and update best when a closer sum is found.
  2. 4Sum (LC 18) — wrap one more loop around 3Sum. Fix two outer elements, run two-pointer on the remaining window. O(n³) time; duplicate-skipping at all four levels.

Interview Disguises:

  • "A fraud detection system scans a sorted transaction ledger for any three transactions that net to zero — a sign of a concealed round-trip transfer." — 3Sum exactly; sort the amounts, fix one, two-pointer the rest.
  • "Given sorted load readings from three components, find all combinations where combined load is zero (a perfectly balanced system)." — same algorithm; domain is physical load balancing instead of money.

Problem 9: Sort Colors (Dutch National Flag) ⭐ MUST DO

Difficulty Time to Solve Pattern
Medium 20-25 min Three Pointers

Sort array containing only 0s, 1s, and 2s in-place, in a single pass.

Example:

Input:  nums = [2, 0, 2, 1, 1, 0]
Output: [0, 0, 1, 1, 2, 2]

Input:  nums = [2, 0, 1]
Output: [0, 1, 2]

Three Pointer Regions:

[0, 0, ... | 1, 1, ... | unprocessed... | 2, 2, ...]
 ^left      ^curr                         ^right
  • Everything before left → all 0s
  • Between left and curr → all 1s
  • Everything after right → all 2s
  • Between curr and right → unprocessed

Why curr does NOT advance when swapping with right: When we swap nums[curr] with nums[right], the element coming from right is unprocessed — it could be 0, 1, or 2. We must check it before advancing. But when swapping with left, the element coming from left is always 1 (it was already processed), so it's safe to advance curr.

Solution:

public void sortColors(int[] nums) {
    int left = 0;       // Boundary for 0s
    int curr = 0;       // Current element being examined
    int right = nums.length - 1;  // Boundary for 2s

    while (curr <= right) {
        if (nums[curr] == 0) {
            swap(nums, left, curr);
            left++;
            curr++;  // Safe: element from left was already processed (it's a 1)
        } else if (nums[curr] == 2) {
            swap(nums, curr, right);
            right--;
            // Do NOT advance curr — swapped element is unprocessed
        } else {
            curr++;  // It's a 1, already in correct region
        }
    }
}

private void swap(int[] nums, int i, int j) {
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

Complexity:

Time O(n) — single pass
Space O(1) — in-place

Real World: Partitioning a task queue into three priority buckets (HIGH/MEDIUM/LOW) in a single scan with no extra memory — used in real-time schedulers where allocation is expensive. Also used in database engines to physically reorder rows by a three-value category column in place.

Variations:

  1. Two-category partition — simpler case: partition into elements < x and elements >= x. Standard fast/slow two-pointer; no third pointer needed. This is Quicksort's partition step.
  2. k distinct values — for more than three categories, Dutch flag no longer applies cleanly. Use counting sort: count occurrences of each value, then overwrite the array. O(n) time, O(k) space.

Interview Disguises:

  • "A hospital triage system has patients labeled CRITICAL (0), URGENT (1), or ROUTINE (2) mixed in an intake list. Reorder the list in place in a single pass so critical patients are treated first." — sort colors exactly; three-value Dutch flag.
  • "A task scheduler receives jobs tagged HIGH (0), MEDIUM (1), LOW (2) in arbitrary order. Sort them in place without extra memory so the CPU always picks the highest-priority job next." — same three-pointer Dutch flag; scheduler framing.

Problem 10: Valid Triangle Number

Difficulty Time to Solve Pattern
Medium 20-25 min Opposite Direction + Loop

Count the number of triplets that could form the sides of a triangle.

Triangle inequality: for sides a, b, c where a <= b <= c, we only need to check a + b > c (the other two inequalities are automatically satisfied when the array is sorted).

Example:

Input:  nums = [2, 2, 3, 4]
Output: 3
         Valid triplets: (2,3,4), (2,3,4), (2,2,3)

Input:  nums = [4, 2, 3, 4]
Output: 4

Key Insight: After sorting, fix the largest side at index i, then use two pointers on [0..i-1]. If nums[left] + nums[right] > nums[i], then every element from left to right-1 paired with right also satisfies the condition (because they're all >= nums[left]). So we can count right - left valid triplets at once.

Solution:

public int triangleNumber(int[] nums) {
    Arrays.sort(nums);
    int count = 0;

    // Fix the largest side, work backwards
    for (int i = nums.length - 1; i >= 2; i--) {
        int left = 0;
        int right = i - 1;

        while (left < right) {
            if (nums[left] + nums[right] > nums[i]) {
                // All elements from left to right-1 also form valid triangles with right and i
                count += right - left;
                right--;
            } else {
                left++;  // Sum too small, need larger left side
            }
        }
    }

    return count;
}

Complexity:

Time O(n^2) — sort O(n log n) + nested two-pointer O(n^2)
Space O(1) — in-place sort

Real World: Structural engineering validation: given a sorted list of beam lengths, count how many sets of three beams satisfy the triangle inequality and can form a valid triangular support. Also used in computational geometry to count valid triangulations.

Variations:

  1. Return the actual triplets — instead of counting, collect each valid triplet. Same loop structure; replace count += right - left with a nested loop from left to right - 1 to emit each pair.
  2. Count invalid triangles — total triplets is C(n,3) = n*(n-1)*(n-2)/6. Subtract the valid count to get invalid ones. No extra code needed beyond this problem's solution.

Interview Disguises:

  • "A structural engineer has a sorted list of beam lengths. How many sets of three beams can form a valid triangular support frame?" — triangle inequality on sorted array; fix largest side, two-pointer the rest.
  • "Given sorted cable lengths for a suspension bridge, count how many three-cable combinations satisfy the triangle inequality required for stable geometry." — same algorithm; engineering framing makes it sound different.

Problem 11: Trapping Rain Water ⭐ MUST DO

Difficulty Time to Solve Pattern
Hard 30-40 min Opposite Direction

Given an array of bar heights, compute how much rainwater gets trapped between the bars after it rains.

Example:

Input:  height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
Output: 6

Input:  height = [4, 2, 0, 3, 2, 5]
Output: 9

Visual:

#  = bar    ~  = trapped water    ·  = air

·  ·  ·  ·  ·  ·  ·  #  ·  ·  ·  ·
·  ·  ·  #  ~  ~  ~  #  #  ~  #  ·
·  #  ~  #  #  ~  #  #  #  #  #  #
0  1  0  2  1  0  1  3  2  1  2  1

6 units of water (each ~ = 1 unit)

Key Insight:

  • Water at any position = min(maxLeft, maxRight) - height[i] — capped by the shorter wall on either side
  • Naive solution: precompute leftMax and rightMax arrays → O(n) space. Two pointers eliminate those arrays → O(1) space
  • Always process the shorter side. When height[left] < height[right]:
    • The right pointer is a guaranteed wall at least as tall as height[right], which is already taller than the current left
    • So the right side is not the bottleneck here — maxLeft alone determines the water level at left
    • Compute water, update maxLeft, advance left
  • Symmetric when height[right] <= height[left] — process right using maxRight

Solution:

public int trap(int[] height) {
    int left = 0, right = height.length - 1;
    int maxLeft = 0, maxRight = 0;
    int water = 0;

    while (left < right) {
        if (height[left] < height[right]) {
            // Right is a guaranteed wall — left side is the bottleneck
            if (height[left] >= maxLeft) {
                maxLeft = height[left];  // new left wall
            }
            else {
                water += maxLeft - height[left];  // valley
            }
            left++;
        } else {
            // Left is a guaranteed wall — right side is the bottleneck
            if (height[right] >= maxRight) {
                maxRight = height[right];  // new right wall
            }
            else {
                water += maxRight - height[right];  // valley
            }
            right--;
        }
    }

    return water;
}

Complexity:

Time O(n) — single pass with two pointers
Space O(1) — four variables, no extra arrays

Real World: Flood modeling: given a terrain elevation profile, calculate how much rainwater pools in valleys after a storm. Used in GIS civil engineering tools for runoff estimation and in chip design to model fluid flow across a circuit board surface.

Variations:

  1. Trapping Rain Water II (LC 407) — 2D grid instead of 1D array. Two-pointer no longer works; use a min-heap BFS that processes boundary cells in order of height (water level is determined by the lowest surrounding wall).
  2. Volume of histogram — same as trapping rain water but you're asked for total bar area instead of trapped water. Flip the formula: sum height[i] instead of maxLeft/Right - height[i]. Tests whether you understand what the two pointers are actually computing.

Interview Disguises:

  • "Given an elevation cross-section of a valley as an array of heights, calculate how much rainwater accumulates in the low points after a storm." — trapping rain water verbatim; process the shorter side first.
  • "A drainage engineer models a pipe cross-section as an array of wall heights. How much fluid collects in the cavities between the walls?" — same min(maxLeft, maxRight) - height[i] formula; pipe/fluid framing.

Quick Reference Table

# Problem Pattern Key Technique Time
1 Reverse String Opposite Simple swap from both ends O(n)
2 Two Sum II Opposite Sorted array property O(n)
3 Valid Palindrome Opposite Skip non-alphanumeric O(n)
4 Remove Duplicates Same (Fast/Slow) Overwrite in-place O(n)
5 Move Zeroes Same (Fast/Slow) Swap to maintain order O(n)
6 Sorted Squares Opposite Fill from end O(n)
7 Container with Water Opposite Greedy — move shorter side O(n)
8 3Sum Opposite + Loop Sort first, fix one element, skip duplicates O(n^2)
9 Sort Colors Three Pointers Dutch flag — don't advance on right swap O(n)
10 Valid Triangle Opposite + Loop Sort + fix largest side, count in bulk O(n^2)
11 Trapping Rain Water Opposite Track max heights, process bottleneck O(n)

When to Use Each Pattern

Opposite Direction Pattern

  • Array is sorted
  • Need to find pairs/triplets with specific sum
  • Need to check palindrome properties
  • Need to reverse or swap elements from ends
  • Working with two ends of array simultaneously

Same Direction (Fast/Slow) Pattern

  • Need to remove/filter elements in-place
  • Need to partition array
  • Need to modify array while iterating
  • Overwriting array elements
  • Maintaining relative order

Three Pointers Pattern

  • Need to partition into 3 sections
  • Dutch National Flag problem
  • Problems with 3 distinct values

Common Mistakes to Avoid

General Two Pointer Mistakes

  1. Off-by-one in loop conditionleft < right vs left <= right (use <= only when you need to process the meeting point, e.g., Sort Colors)
  2. Not handling empty/single element arrays — Always consider edge cases before the loop
  3. Moving the wrong pointer — Think carefully about which pointer movement narrows the search correctly

Opposite Direction Mistakes

  1. Forgetting to sort when the algorithm requires sorted input (3Sum, Valid Triangle)
  2. Moving both pointers when only one should move (Container with Water — only move the shorter side)
  3. Not skipping duplicates at all three levels in 3Sum (outer loop + both inner pointers)

Same Direction Mistakes

  1. Using overwrite when swap is needed — Move Zeroes needs swap to preserve zeros; Remove Duplicates can overwrite since we don't care about the tail
  2. Not maintaining relative order — Same direction preserves order; be careful not to break this

Three Pointer Mistakes

  1. Advancing curr after swapping with right in Sort Colors — the swapped element is unprocessed
  2. Wrong loop condition — Sort Colors uses curr <= right (not <) because right boundary is exclusive