Two Pointers LeetCode Pattern Guide — Opposite-Ends, Fast-Slow, and Partitioning
A two pointers LeetCode pattern guide for opposite-ends scans, fast-slow pointers, sliding windows, linked-list cycles, and partitioning. Learn the invariants that make pointer movement safe.
Two Pointers LeetCode Pattern Guide — Opposite-Ends, Fast-Slow, and Partitioning
A two pointers LeetCode pattern guide is a guide to controlled movement. The technique works when two indexes, references, or boundaries can move through data without revisiting every pair. Opposite-ends pointers shrink a sorted search space. Fast-slow pointers detect cycles or remove offset nodes. Same-direction pointers maintain windows or write positions. Partitioning pointers rearrange arrays in place. The key is not "use two variables." The key is proving that each pointer move discards work safely.
Two pointers LeetCode pattern guide: choose the pointer shape
| Problem shape | Pointer pattern | Example | |---|---|---| | Pair in sorted array | Opposite ends | Two sum II | | Palindrome check | Opposite ends | Valid palindrome | | Container area | Opposite ends | Container with most water | | Linked list cycle | Fast-slow | Floyd's cycle detection | | Remove nth from end | Gap pointers | Fast pointer n ahead | | Move zeros / remove duplicates | Read-write | Stable compaction | | Sort colors / partition | Three-way partition | Dutch national flag | | Longest substring/window | Sliding window | Expand right, shrink left |
Before coding, ask what each pointer represents. A left/right pair may represent the remaining candidate range. Fast/slow may represent speed difference or fixed gap. Read/write may represent scanned input vs next output location.
Opposite-ends pointers on sorted arrays
The classic sorted two-sum pattern starts with left = 0, right = n - 1. If nums[left] + nums[right] is too small, move left rightward to increase the sum. If too large, move right leftward to decrease it. This works only because the array is sorted. The invariant is that pairs outside the current range have been proven unnecessary.
Use opposite ends for:
- Two Sum II.
- Three sum after sorting.
- Pair with target difference.
- Squaring a sorted array.
- Valid palindrome with skips.
- Container with most water.
For three sum, sort first, fix one index, then run two-sum on the remaining range. Skip duplicates at both the fixed index and pointer movements. The common bug is skipping duplicates too early and accidentally missing a valid combination. A safe pattern: record a found triplet, then move both pointers past repeated values.
For sorted squares, the largest square is at one of the ends because negative values can have large absolute value. Fill the output array from the back, comparing abs(left) and abs(right). This is a clean example of two pointers replacing sort.
Container with most water: a proof pattern
Container with most water is the interviewer's favorite test of whether you understand pointer movement. Area is width times the shorter height. Start at both ends. Move the pointer with the shorter line.
Why is that safe? Suppose left is shorter. Any container using the same left boundary with a smaller width cannot beat the current area unless it finds a taller shorter-side, but the left height caps all those areas. Keeping left and moving right inward only reduces width while the left cap remains. So discard left and search for a taller left boundary.
That proof style applies broadly. A pointer move is correct when you can explain why the skipped options cannot improve the answer. If you cannot prove that, you may need a different algorithm.
Palindrome and tolerant matching
Palindrome checks use left and right pointers moving inward. For a basic alphanumeric palindrome:
- Move left until it points to a valid character.
- Move right until it points to a valid character.
- Compare normalized characters.
- If mismatch, return false.
- Move both inward.
For "valid palindrome II" with at most one deletion, on first mismatch check either skipping left or skipping right. Do not greedily skip one side without testing both possibilities. A helper is_pal(l, r) keeps the code clear.
This pattern shows up in string cleanup too: reverse vowels, compare backspaces, merge from end. Whenever the important comparison is symmetric from both ends, consider opposite pointers.
Fast-slow pointers in linked lists
Fast-slow pointers use different speeds or a fixed gap. The linked-list cycle problem uses Floyd's algorithm: slow moves one step, fast moves two. If there is a cycle, fast eventually meets slow. If fast reaches null, there is no cycle.
To find the cycle entry, after meeting, move one pointer to head and keep the other at the meeting point. Move both one step. They meet at the cycle start. You do not need to derive the math in every interview, but you should know the result and be able to state that the distances align after the first meeting.
Other fast-slow problems:
- Middle of linked list: fast moves two, slow moves one.
- Remove nth from end: fast starts n steps ahead, then both move until fast reaches end.
- Reorder list: find middle, reverse second half, merge alternating.
- Palindrome linked list: find middle, reverse second half, compare halves.
The traps are null checks and even-length behavior. For middle node, decide whether you want the first middle or second middle. The loop condition while fast and fast.next returns the second middle for even lengths. If the prompt requires first middle, adjust.
Read-write pointers for in-place compaction
Read-write pointers are two pointers moving in the same direction. One scans every item; the other marks where the next kept item should be written.
Examples:
- Remove duplicates from sorted array.
- Remove element.
- Move zeroes.
- Compress string.
- Stable partition by condition.
For remove duplicates, write represents the length of the unique prefix. Iterate read from 1 to end. If nums[read] != nums[write - 1], assign nums[write] = nums[read] and increment write. Return write.
For move zeroes, scan all numbers. When a nonzero appears, write it at write and increment. After the scan, fill the rest with zeroes. This preserves nonzero order. If the prompt cares about minimizing writes, use swap-based logic; otherwise the two-pass version is clearer.
The invariant is: everything before write is already correct. Everything from read onward is unprocessed. This phrase makes the pattern easy to explain.
Partitioning and Dutch national flag
Partitioning pointers rearrange elements around a pivot or categories. The Dutch national flag problem sorts 0s, 1s, and 2s in one pass using three regions:
[0, low)are 0s.[low, mid)are 1s.[mid, high]are unknown.(high, end]are 2s.
Process mid:
- If value is 0, swap with low, increment both low and mid.
- If value is 1, increment mid.
- If value is 2, swap with high, decrement high, but do not increment mid because the swapped-in value is unknown.
That last line is the common bug. After swapping with high, the new mid value has not been processed.
Partitioning also appears in quicksort, quickselect, move negatives before positives, and segregate odd/even. The exact regions change, but the interview skill is naming the invariant regions.
Sliding window: related but not identical
Sliding window is often considered a two-pointer pattern because left and right boundaries move through an array or string. Use it when the problem asks for a contiguous subarray or substring and the window can be adjusted incrementally.
There are two major types:
- Fixed-size window: maintain exactly k items and update counts/sum as right advances.
- Variable-size window: expand right until invalid or sufficient, then shrink left while preserving the desired condition.
Examples:
- Longest substring without repeating characters.
- Minimum window substring.
- Max consecutive ones with at most k flips.
- Longest subarray with sum at most target when all numbers are nonnegative.
The monotonic condition matters. For positive numbers, increasing right increases sum and moving left decreases sum. With negative numbers, that monotonic behavior breaks, and a prefix sum/hash map may be needed instead.
For minimum window substring, the invariant is not just counts in the window; it is how many required characters are currently satisfied. Shrink only while the window is valid, updating the best answer before removing the left character that might break validity.
When two pointers do not apply
Do not force two pointers when the data lacks ordering or monotonicity. Unsorted two-sum is a hash map problem, not opposite-ends pointers, unless you are allowed to sort and return values rather than original indices. Subarray sum with negative numbers is usually prefix sums, not sliding window. Arbitrary pair counting may need sorting, binary search, or hashing.
A good interview move is to say: "If the array were sorted, two pointers would work. Because it is not sorted and we need original indices, I will use a hash map." That shows pattern judgment.
Common traps in two-pointer problems
Moving the wrong pointer. Every move needs a reason tied to sortedness, area cap, validity, or invariant regions.
Losing original indices after sorting. If the output requires original positions, store value-index pairs or use a hash map.
Duplicate handling. Three sum and pair counting need explicit duplicate skipping.
Null pointer crashes. Linked-list fast pointers require fast and fast.next checks.
Window condition with negatives. Sliding window sum patterns often require nonnegative values.
Forgetting to process swapped-in values. In three-way partition, do not increment mid after swapping with high.
Infinite loops. Ensure at least one pointer moves on every branch.
Prep checklist for two pointers
Before coding, answer:
- Are the inputs sorted, or can I sort them?
- Do I need original indices?
- What does each pointer represent?
- What invariant is true before and after each move?
- Why is moving this pointer safe?
- Are duplicates allowed?
- Does the answer need contiguous elements?
- Can the window condition be maintained monotonically?
- What are the null or boundary cases?
Most two-pointer solutions are O(n) after sorting, or O(n log n) including the sort. Space is often O(1) besides output, unless sorting or maps are needed.
How to talk about two pointers in interviews and resumes
In an interview, do not just say "two pointers." Say the invariant. For sorted two-sum: "The sum is too small, so any pair using the current left with a smaller right would be even smaller. I can safely increment left." For read-write compaction: "The prefix before write is the cleaned result, and read scans candidates." For fast-slow: "The gap ensures that when fast reaches the end, slow is at the node before the one to remove."
On a resume, translate the pattern into performance and memory impact:
- "Replaced nested pair scan with sorted two-pointer pass, reducing matching from quadratic to near-linear after sort."
- "Implemented in-place compaction for event arrays using read/write boundaries to avoid extra allocations."
- "Built streaming window validation for user sessions with moving boundaries and incremental counts."
The pattern is valuable because it removes wasted comparisons. When pointer movement is justified by an invariant, the solution feels inevitable instead of clever.
Related guides
- Backtracking LeetCode Pattern Guide — Permutations, Combinations, and Subsets — A practical backtracking LeetCode pattern guide with templates, decision rules, duplicate-handling tricks, pruning advice, and interview talk tracks for permutations, combinations, subsets, and constraint problems.
- BFS LeetCode Pattern Guide — Shortest-Path, Level-Order, and Multi-Source — A BFS LeetCode pattern guide for recognizing queue-based problems, implementing level-order traversal, solving unweighted shortest paths, using multi-source BFS, and avoiding common visited-state bugs.
- Binary Search LeetCode Pattern Guide — Bounds, Rotated Arrays, and Search-on-Answer — A binary search LeetCode pattern guide for exact search, lower/upper bounds, rotated arrays, and search-on-answer problems. Includes invariants, templates, traps, and interview-ready explanations.
- Bit Manipulation LeetCode Pattern Guide — XOR Tricks, Masks, and Counting Bits — A practical bit manipulation LeetCode pattern guide covering XOR tricks, masks, counting bits, subset enumeration, flags, and the interview pitfalls around signed integers.
- DFS LeetCode Pattern Guide — Recursion Templates and Grid/Graph Problems — A DFS LeetCode pattern guide with recursion templates, visited-set rules, grid and graph examples, backtracking structure, complexity notes, and interview pitfalls.
