Two Pointers Pattern: Algorithmic Intuition, Sub-patterns & C++ Guide
Keep two indexes into the data and move them toward the answer, throwing away possibilities you can prove are useless.
4 sub-patterns Β· 22 questions
What is this pattern?
Two Pointers means keeping two positions in the data and moving them on purpose, instead of trying every pair.
There are two shapes:
- Opposite ends: one pointer starts at the left, one at the right, and they walk towards each other. Each step, one of them moves β the one that can't lead to a better answer.
- Same direction: both pointers walk left to right. One
reads every element; the other marks where the next element we want towritegoes.
The trick is always the same: every move throws away options you can prove are useless, so you never need to check them.
Two people searching a sorted bookshelf for two books whose prices add up to βΉ500 β one starts at the cheap end, one at the expensive end. If the pair is too cheap, the cheap-end person steps forward. If it's too expensive, the expensive-end person steps back. They never need to go back.
See it move
Find two numbers that add up to 10 in a sorted array. Each step throws away one number for good.
arr = [1, 3, 4, 6, 8, 11], target = 10step 1/6- target
- 10
- a[L] + a[R]
- β
L starts at the smallest number, R at the largest. Everything outside [L, R] will be ruled out for good.
Remove duplicates from a sorted array in place. The green region is the finished answer.
nums = [1, 1, 2, 2, 2, 3, 4, 4]step 1/9Everything before `write` is the final answer. `read` looks at each element once.
How do I recognise it?
The array is sorted (or you're allowed to sort it) and you need a pair / triplet with some sum or property.
You compare things from both ends: palindromes, reversing, the two ends competing (largest square, tallest wall).
You must change an array in place with O(1) extra space: remove, deduplicate, move zeros, compact.
Elements fall into a few fixed groups (0/1/2, even/odd, less/equal/greater than a pivot) and must be rearranged.
You walk two sequences together (is
sa subsequence oft, merge two sorted lists).
Looks like Two Pointers, but isn't
Sorting would scramble the original indices, and sorting costs O(N log N) anyway. A hash map finds the partner in one pass.
Two pointers walking the same way is a window β but when you care about everything between them (a sum, a count), it's the Sliding Window pattern with its validity rule.
You need a contiguous range, not two separate elements β and with negatives no pointer move is safe.
Opposite-end moves only make sense when the order tells you which side to move. No order β no safe move.
How would I discover it from scratch?
Don't memorise βuse two pointersβ. Ask these questions in order β the pattern falls out.
Try every pair (i, j) with two nested loops β O(NΒ²). For triplets, three loops β O(NΒ³).
Sub-patterns
Same core idea, different rule for βvalidβ. Learn them in this order.
Opposite ends
8 questionsOne pointer at each end, walking towards each other. Each step compares the two ends and moves the side that can't do better.
Same direction (read / write)
5 questionsBoth pointers walk left to right. read looks at every element; write is where the next element you keep goes.
Triplets / k-sum
6 questionsFind triplets (or quadruplets) with a target sum: sort, fix one element, and solve the rest as a two-pointer pair problem.
Partition / Dutch National Flag
3 questionsRearrange elements into a few fixed groups in place. Each finished group grows behind its own pointer.
Don't mix these up
Are you comparing the two ends to decide a move (opposite)? Or is one pointer just collecting what the other one reads (same direction)?
If you care about two specific elements (a pair, two ends), it's two pointers. If you care about everything between them (sum, counts, distinct values), it's a sliding window.
Sorted β two pointers, O(1) space. Unsorted and you need original indices β hash map; sorting would lose the indices.
DNF swaps elements across the array, so the original order is not kept. If the problem says βkeep the relative orderβ, use passes or fill from both ends instead.
Code templates (C++)
Opposite ends (sorted pair)
The comparison decides which side moves. Every move discards an element that can't be in any valid pair.
// Opposite ends on a sorted array: find a pair with the given sum.
// Each step throws away one element that can never be part of the answer.
pair<int, int> pairWithSum(vector<int>& a, int target) {
int left = 0, right = (int)a.size() - 1;
while (left < right) {
int sum = a[left] + a[right];
if (sum == target) return {left, right};
if (sum < target) left++; // need bigger: a[left] is too small for every partner
else right--; // need smaller: a[right] is too big for every partner
}
return {-1, -1};
}Same direction (read / write)
Only the keep rule changes between problems: not equal to a value, different from the last written, not zeroβ¦
// Same direction (read / write): keep only the elements you want, in place.
// Everything before `write` is the final answer so far.
int keepIf(vector<int>& a, int removeValue) {
int write = 0;
for (int read = 0; read < (int)a.size(); read++) {
if (a[read] != removeValue) { // the "keep" rule changes per problem
a[write] = a[read];
write++;
}
}
return write; // new length: a[0 .. write-1]
}Triplets / k-sum
Sort, fix one, two-pointer the rest. The duplicate-skipping lines are what make the triplets unique.
// Triplets: sort, fix one element, then run opposite-ends two pointers on the rest.
vector<vector<int>> threeSum(vector<int> a, int target) {
sort(a.begin(), a.end());
vector<vector<int>> res;
int n = a.size();
for (int i = 0; i < n - 2; i++) {
if (i > 0 && a[i] == a[i - 1]) continue; // same fixed value β same triplets
int left = i + 1, right = n - 1;
while (left < right) {
long long sum = (long long)a[i] + a[left] + a[right];
if (sum < target) left++;
else if (sum > target) right--;
else {
res.push_back({a[i], a[left], a[right]});
while (left < right && a[left] == a[left + 1]) left++; // skip duplicates
while (left < right && a[right] == a[right - 1]) right--;
left++, right--;
}
}
}
return res;
}Partition (Dutch National Flag)
Three regions grow from the edges. Note when mid moves and when it doesn't.
// Dutch National Flag: sort 0s, 1s and 2s in one pass.
// [0 .. low-1] = 0s, [low .. mid-1] = 1s, [mid .. high] = unknown, [high+1 .. n-1] = 2s
void dutchFlag(vector<int>& a) {
int low = 0, mid = 0, high = (int)a.size() - 1;
while (mid <= high) {
if (a[mid] == 0) swap(a[low++], a[mid++]); // a 0 joins the left region
else if (a[mid] == 1) mid++; // a 1 is already in place
else swap(a[mid], a[high--]); // a 2 joins the right region; don't move mid β
// the value swapped in hasn't been checked yet
}
}