When you work with an array, you usually find what you need in one of two ways: either you already know the exact position (the index) of an item, or you walk through the array one item at a time using a loop. That walk is called a traversal.
Most of the time we traverse an array in a single direction — either from the first item to the last, or from the last item back to the first. Which direction we pick depends on the problem.
But some problems need us to look at items from both ends at the same time. The slow way to do this is with nested loops (a loop inside another loop). Nested loops re-check the same items again and again, which wastes time. The two-pointer technique gives us a much faster way.
A pointer here is just a variable that holds an index — it "points at" a position in the array. The two-pointer technique uses two of these variables, named left and right:
left starts at the beginning, index 0.right starts at the end, index n - 1, where n is the size of the array.Think of two fingers placed on a row of boxes: one finger on the first box, one on the last box. They move toward each other until they meet in the middle.
The general idea is simple:
left = 0 and right = n - 1 (so left < right).left < right. On each pass:arr[left] and arr[right] and do whatever the problem needs (compare them, add them, swap them, and so on).left forward by some number of steps if it should move.right backward by some number of steps if it should move.So the pointers start far apart and slowly close in:
left -> [ v1 v2 ... ... ... vn ] <- right
left moves right, right moves left, until they cross
Imagine the array arr = {10, 20, 30, 40}, so n = 4.
left = 0, right = 3. We look at arr[0] = 10 and arr[3] = 40.left = 1, right = 2. We look at arr[1] = 20 and arr[2] = 30.left would reach 2 and right would reach 1, so left < right is false. We stop.In just two passes we touched every item. No nested loops were needed.
Here is the shape of a two-pointer function (shown in C++). The helper functions incrementLeft, decrementRight, leftStep, and rightStep simply decide *whether* and *by how much* each pointer should move — and most of the time they are tiny and can be written directly inline.
void twoPointer(const vector<int>& arr) {
// Initialize left and right to the ends of the array
int left = 0;
int right = arr.size() - 1;
while (left < right) {
int leftVal = arr[left];
int rightVal = arr[right];
// Check if the left pointer should be incremented
if (incrementLeft(leftVal, rightVal)) {
// Increment the left pointer by some steps
left += leftStep(leftVal, rightVal);
// ...
}
// similarly decide whether to move right
}
}
This is the big win: a problem that might have needed nested loops (slow) can often be solved in a single linear pass with constant extra memory.
Many array problems fit the two-pointer pattern, but it is not always obvious how. They generally fall into three groups:
Learning to recognize which group a problem belongs to is the real skill, and it comes with practice.