Two Pointers: Overview
What Is the Two Pointers Technique?
The two pointers technique uses two index variables that move through a data structure (usually an array or string) according to certain rules. By maintaining these two references simultaneously, you can often reduce an O(n2) brute-force search to O(n) time.
Think of it like reading a book with two bookmarks. Instead of checking every pair of pages, you advance each bookmark based on what you have already seen. The key insight: the relationship between the two positions lets you skip large portions of the search space.
Two pointers is not a single algorithm. It is a family of patterns that share the idea of tracking two positions and moving them strategically.
Why Does It Work?
In a brute-force approach, checking all pairs in an array of length n requires O(n2) comparisons. Two pointers achieves O(n) because each pointer moves in one direction and never backtracks. Since each pointer visits at most n elements, the total work is at most 2n, which is O(n).
This works when the problem has a monotonic property: moving one pointer in a certain direction guarantees that the optimal position for the other pointer either stays or moves in a predictable direction.
The Monotonicity Principle
If increasing the left pointer can only cause the optimal right to stay or increase, you never need to re-examine earlier positions. This is what makes two pointers linear.
The Three Main Patterns
Most two-pointer problems fall into one of three categories. Understanding which pattern applies is the first step to solving any two-pointer problem.
1. Opposite Ends
One pointer starts at the beginning, the other at the end. They move toward each other until they meet.
When to use: Sorted arrays, palindrome checks, pair-sum problems.
Examples: Two Sum (sorted), Container With Most Water, Valid Palindrome.
2. Same Direction
Both pointers start at (or near) the beginning and move in the same direction, but at different speeds or conditions.
When to use: Removing duplicates, partitioning, read/write pointer problems.
Examples: Remove Duplicates, Move Zeroes, Partition Array.
3. Fast and Slow
One pointer moves one step at a time, the other moves two steps. Used mainly with linked lists.
When to use: Cycle detection, finding the middle element, detecting loops.
Examples: Linked List Cycle, Find Middle, Happy Number.
Visual: How Pointers Move
These animations show two of the main patterns. Step through to see how two pointers traverse an array in each case.
Opposite Ends Pattern
Array: [1, 3, 5, 7, 9, 11, 13]. Left pointer (green) starts at index 0, right pointer (orange) at index 6. They move toward each other.
Same Direction Pattern
Array: [0, 1, 0, 3, 0, 5, 0]. Write pointer (green) marks where to place non-zero values. Read pointer (orange) scans every element.
When to Use Two Pointers
Look for these signals in a problem statement:
- The input is sorted (or you can sort it without losing information).
- You need to find a pair or subarray satisfying some condition.
- The problem asks to do something in-place with O(1) extra space.
- You need to compare elements from both ends (palindrome, container, etc.).
- A brute-force nested loop checks all pairs, and you suspect O(n) is possible.
Two Pointers vs Other Techniques
| Technique | Time | Space | Best For |
|---|---|---|---|
| Brute Force (nested loops) | O(n2) | O(1) | Small inputs, baseline |
| Hash Map | O(n) | O(n) | Unsorted arrays, frequency counting |
| Two Pointers | O(n) | O(1) | Sorted arrays, in-place operations |
| Sliding Window | O(n) | O(1) to O(k) | Contiguous subarrays of variable length |
| Binary Search | O(n log n) | O(1) | Sorted arrays, decision problems |
Time and Space Complexity
For all three patterns, the key guarantee is:
- Time: O(n). Each pointer traverses at most n elements. Combined, the total work is at most 2n moves.
- Space: O(1). Only two index variables are needed (plus any output array if the problem requires it).
Some variants (like 3Sum) nest two pointers inside an outer loop, giving O(n2). But even then, this is an improvement over the O(n3) brute force.
General Code Templates
Here are the skeletons for each pattern in C++:
Opposite Ends Template
int left = 0, right = n - 1;
while (left < right) {
int current = evaluate(arr, left, right);
if (current == target) {
return {left, right}; // found
} else if (current < target) {
left++; // need larger value
} else {
right--; // need smaller value
}
}
Same Direction Template
int write = 0;
for (int read = 0; read < n; read++) {
if (shouldKeep(arr[read])) {
arr[write] = arr[read];
write++;
}
}
// Elements in [0, write) are the result
Fast and Slow Template
ListNode* slow = head;
ListNode* fast = head;
while (fast != nullptr && fast->next != nullptr) {
slow = slow->next; // 1 step
fast = fast->next->next; // 2 steps
if (slow == fast) {
break; // cycle detected
}
}
Series Roadmap
This series covers the two pointers technique in depth across three focused posts:
- Opposite Ends: Two Sum (sorted), Container With Most Water, Valid Palindrome. Pointers converging from both sides.
- Same Direction and Fast/Slow: Remove Duplicates, Move Zeroes, Partition, cycle detection concepts. Read/write and fast/slow pointer patterns.
- Competition Patterns: 3Sum, Dutch National Flag, Trapping Rain Water. Advanced problems combining multiple patterns.