ππ Two Pointers Technique β Complete Guide
Index: Two Pointers Index | Parent: Array Index
What is the Two Pointers Technique?
Use two variables (pointers) that move through the array to solve problems in O(n) that would normally require O(nΒ²) with nested loops.
When to use Two Pointers?
- Array is sorted (or can be sorted)
- You need to find a pair or triplet that satisfies a condition
- You need to compare elements from both ends
- You need to partition or rearrange elements
- Keywords: "pair sum", "duplicates", "reverse", "palindrome", "remove elements"
Pattern 1: Opposite Direction (Start & End)
Both pointers start at opposite ends and move toward each other.
L β β R
[1, 2, 3, 4, 5, 6, 7]
Template:
function twoPointersOpposite(array $arr): mixed {
$left = 0;
$right = count($arr) - 1;
while ($left < $right) {
// Check condition using $arr[$left] and $arr[$right]
if (/* condition met */) {
return /* result */;
} elseif (/* need larger sum/value */) {
$left++;
} else {
$right--;
}
}
return null; // not found
}
Pattern 2: Same Direction (Fast & Slow)
Both pointers start at beginning. Fast moves ahead, slow follows.
S
F β
[1, 2, 2, 3, 3, 4, 5]
Template:
function twoPointersSameDir(array $arr): array {
$slow = 0;
for ($fast = 1; $fast < count($arr); $fast++) {
if (/* condition */) {
$slow++;
$arr[$slow] = $arr[$fast];
}
}
return array_slice($arr, 0, $slow + 1);
}
Interview Questions β Two Pointers
Q1. Two Sum β Find pair that equals target [E][FB]
Question: In sorted array [1,2,3,4,6], find pair summing to 6. Return indices.
π§ How to Approach This
Step 1 β Recognize the pattern:
The array is sorted. You need a pair with a specific sum. These two signals together = Two Pointers opposite direction.
Step 2 β Brute force (what NOT to do):
Two nested loops check every pair β O(nΒ²). With n=100,000 that's 10 billion operations.
Step 3 β Optimal insight:
Start with the smallest (left) and largest (right) values. Their sum tells you which direction to move:
- Sum too small β need a bigger number β move left pointer right
- Sum too large β need a smaller number β move right pointer left
- Sum matches β done!
Step 4 β Algorithm:
1. Set L=0, R=n-1
2. While L < R: compute sum
3. sum == target β return [L, R]
4. sum < target β L++ (get bigger left value)
5. sum > target β R-- (get smaller right value)
Step 5 β Edge cases:
- Array must be sorted for this to work
- No pair exists: return [] after loop ends
[1, 2, 3, 4, 6] target=6
L R sum=7 > 6 β R--
L R sum=5 < 6 β L++
L R sum=6 β
Found! (indices 1,3)
function twoSum(array $arr, int $target): array {
$left = 0;
$right = count($arr) - 1;
while ($left < $right) {
$sum = $arr[$left] + $arr[$right];
if ($sum === $target) return [$left, $right];
elseif ($sum < $target) $left++;
else $right--;
}
return []; // not found
}
print_r(twoSum([1,2,3,4,6], 6)); // [1, 3] (values 2+4=6)
Time: O(n) | Space: O(1)
JavaScript:
function twoSum(arr, target) {
let left = 0, right = arr.length - 1;
while (left < right) {
const sum = arr[left] + arr[right];
if (sum === target) return [left, right];
sum < target ? left++ : right--;
}
return [];
}
Q2. Three Sum β Find all triplets summing to 0 [M][FB]
Question: Find all unique triplets in [-1,0,1,2,-1,-4] that sum to 0.
π§ How to Approach This
Step 1 β Recognize the pattern:
Two pointers on a sorted array, but for TRIPLETS instead of pairs.
Step 2 β Brute force (what NOT to do):
Three nested loops = O(nΒ³). For n=1000 that's a billion iterations.
Step 3 β Optimal insight:
Fix one element with the outer loop. Now the problem reduces to finding a pair summing to
-arr[i]β which is a two-pointer problem! Sort first to enable skipping duplicates.Step 4 β Algorithm:
1. Sort the array
2. Outer loop: fix arr[i] (i = 0 to n-3)
3. Skip if arr[i] == arr[i-1] (duplicate i)
4. Two pointers: L=i+1, R=n-1; find pair summing to -arr[i]
5. On match: save triplet, skip duplicate L values, skip duplicate R values
Step 5 β Edge cases:
- [0,0,0] β valid triplet
- All positive: no triplets possible (everything > 0)
- Skip duplicates at ALL three levels (i, L, R)
function threeSum(array $arr): array {
sort($arr);
$result = [];
$n = count($arr);
for ($i = 0; $i < $n - 2; $i++) {
// Skip duplicates for first element
if ($i > 0 && $arr[$i] === $arr[$i - 1]) continue;
$left = $i + 1;
$right = $n - 1;
while ($left < $right) {
$sum = $arr[$i] + $arr[$left] + $arr[$right];
if ($sum === 0) {
$result[] = [$arr[$i], $arr[$left], $arr[$right]];
// Skip duplicates
while ($left < $right && $arr[$left] === $arr[$left + 1]) $left++;
while ($left < $right && $arr[$right] === $arr[$right - 1]) $right--;
$left++;
$right--;
} elseif ($sum < 0) {
$left++;
} else {
$right--;
}
}
}
return $result;
}
print_r(threeSum([-1,0,1,2,-1,-4]));
// [[-1,-1,2], [-1,0,1]]
Time: O(nΒ²) | Space: O(1) (not counting output)
Q3. Remove Duplicates from Sorted Array (in-place) [E][FB]
Question: Remove duplicates from [1,1,2,3,3,3,4] in-place. Return new length.
π§ How to Approach This
Step 1 β Recognize the pattern:
In-place rearrangement of a sorted array = Slow/Fast pointer (same direction).
Step 2 β Brute force (what NOT to do):
Copy to new array, filter. Requires O(n) extra space. Interview wants O(1) space.
Step 3 β Optimal insight:
Slow pointer marks the "write position" β where the next unique value should go.
Fast pointer scans ahead. When it finds a value different from arr[slow], copy it to slow+1.
Step 4 β Algorithm:
1. slow = 0 (first unique position)
2. fast = 1 to n-1
3. If arr[fast] β arr[slow]: slow++, arr[slow] = arr[fast]
4. Return slow+1 (count of unique elements)
Step 5 β Edge cases:
- Empty array: return 0
- All same: return 1
- No duplicates: slow follows fast exactly
[1, 1, 2, 3, 3, 3, 4]
S
F arr[F]=1 == arr[S]=1 β skip
F arr[F]=2 != arr[S]=1 β slow++, write 2
S F arr[F]=3 != arr[S]=2 β slow++, write 3
S F arr[F]=3 == arr[S]=3 β skip
F arr[F]=3 == arr[S]=3 β skip
F arr[F]=4 != arr[S]=3 β slow++, write 4
Result: [1,2,3,4,...] length=4
function removeDuplicatesInPlace(array $arr): int {
if (empty($arr)) return 0;
$slow = 0;
for ($fast = 1; $fast < count($arr); $fast++) {
if ($arr[$fast] !== $arr[$slow]) {
$slow++;
$arr[$slow] = $arr[$fast];
}
}
return $slow + 1; // new length
}
echo removeDuplicatesInPlace([1,1,2,3,3,3,4]); // 4
Q4. Container With Most Water [M][FB]
Question: Given heights [1,8,6,2,5,4,8,3,7], find two lines that form container with most water.
π§ How to Approach This
Step 1 β Recognize the pattern:
Maximizing area between two elements β Two pointers (opposite direction).
Step 2 β Brute force (what NOT to do):
Check every pair of walls: O(nΒ²).
Step 3 β Optimal insight:
Area = min(height[L], height[R]) Γ (R - L).
As we move pointers inward, width decreases. Our only hope of increasing area is to find a TALLER wall. So: always move the pointer with the SHORTER wall. Moving the taller wall can never improve area (width shrinks, height is still limited by the shorter side).
Step 4 β Algorithm:
1. L=0, R=n-1, maxArea=0
2. Compute area = min(heights[L], heights[R]) Γ (R-L)
3. Update maxArea
4. Move the shorter pointer inward (if equal, move either)
Step 5 β Edge cases:
- All same heights: area shrinks with every step, max is at outermost pair
- Two elements: only one calculation needed
function maxWater(array $heights): int {
$left = 0;
$right = count($heights) - 1;
$maxArea = 0;
while ($left < $right) {
$area = min($heights[$left], $heights[$right]) * ($right - $left);
$maxArea = max($maxArea, $area);
if ($heights[$left] < $heights[$right]) {
$left++; // move shorter side
} else {
$right--;
}
}
return $maxArea;
}
echo maxWater([1,8,6,2,5,4,8,3,7]); // 49
Time: O(n) | Space: O(1)
Q5. Valid Palindrome [E][FB]
Question: Is "A man, a plan, a canal: Panama" a palindrome (ignoring non-alphanumeric)?
π§ How to Approach This
Step 1 β Recognize the pattern:
Checking both ends of a sequence = Two Pointers (opposite direction).
Step 2 β Key insight:
Process only alphanumeric characters. Compare lowercase versions of chars at L and R.
Skip non-alphanumeric chars by advancing the relevant pointer.
Step 3 β Algorithm:
1. L=0, R=strlen(s)-1
2. While L < R: skip non-alphanumeric on both sides
3. If lowercase s[L] β lowercase s[R] β false
4. Advance both L++ R--
5. Return true
Step 4 β Edge cases:
- Empty string β true (palindrome by definition)
- All non-alphanumeric β true (empty after filtering)
- Single character β true
function isPalindrome(string $s): bool {
$left = 0;
$right = strlen($s) - 1;
while ($left < $right) {
// Skip non-alphanumeric from left
while ($left < $right && !ctype_alnum($s[$left])) $left++;
// Skip non-alphanumeric from right
while ($left < $right && !ctype_alnum($s[$right])) $right--;
if (strtolower($s[$left]) !== strtolower($s[$right])) return false;
$left++;
$right--;
}
return true;
}
var_dump(isPalindrome("A man, a plan, a canal: Panama")); // true
var_dump(isPalindrome("race a car")); // false
function isPalindrome(s) {
s = s.toLowerCase().replace(/[^a-z0-9]/g, '');
let left = 0, right = s.length - 1;
while (left < right) {
if (s[left] !== s[right]) return false;
left++; right--;
}
return true;
}
Q6. Trap Rainwater [H][FB]
Question: Given [0,1,0,2,1,0,1,3,2,1,2,1], how much water is trapped?
π§ How to Approach This
Step 1 β Recognize the pattern:
Each position traps water equal to min(maxLeft, maxRight) - height[i]. Two Pointers lets us compute this in O(1) space.
Step 2 β Brute force (what NOT to do):
For each index, scan left for maxLeft and scan right for maxRight β O(nΒ²).
Step 3 β Pre-computed arrays (better):
Pre-compute leftMax[] and rightMax[] arrays, then a single pass for water. O(n) time, O(n) space.
Step 4 β Two Pointer insight (best β O(1) space):
The water at position i is bounded by min(maxLeft, maxRight). If height[L] < height[R], then we KNOW the left side's bound is maxLeft (right side is at least height[R] which is bigger). Process the left side with confidence.
Step 5 β Algorithm:
1. L=0, R=n-1, maxLeft=0, maxRight=0, water=0
2. While L < R:
- If heights[L] <= heights[R]: process left side
- heights[L] >= maxLeft? β new maxLeft (no water here)
- else β water += maxLeft - heights[L]
- Else: process right side symmetrically
Step 6 β Edge cases:
- Monotonically increasing/decreasing β no water trapped
[0,1,0,2,1,0,1,3,2,1,2,1]
β β
water water
Total = 6
function trapRainwater(array $heights): int {
$left = 0;
$right = count($heights) - 1;
$maxLeft = 0;
$maxRight = 0;
$water = 0;
while ($left < $right) {
if ($heights[$left] <= $heights[$right]) {
if ($heights[$left] >= $maxLeft) {
$maxLeft = $heights[$left]; // update max, no water here
} else {
$water += $maxLeft - $heights[$left]; // trapped water
}
$left++;
} else {
if ($heights[$right] >= $maxRight) {
$maxRight = $heights[$right];
} else {
$water += $maxRight - $heights[$right];
}
$right--;
}
}
return $water;
}
echo trapRainwater([0,1,0,2,1,0,1,3,2,1,2,1]); // 6
Time: O(n) | Space: O(1)
Q7. Move Zeros to End [E][FB][L]
Question: Move all zeros to end while maintaining order of non-zeros.
[0,1,0,3,12] β [1,3,12,0,0]
π§ How to Approach This
Step 1 β Recognize the pattern:
In-place rearrangement maintaining relative order = Slow/Fast pointers (same direction).
Step 2 β Key insight:
Slow pointer tracks the "write position" for non-zeros. Fast scans for non-zero values and writes them to slow position. After the loop, fill remaining positions with zeros.
Step 3 β Algorithm:
1. slow = 0; for fast in 0..n-1: if arr[fast] β 0 β arr[slow] = arr[fast], slow++
2. Fill arr[slow..n-1] with zeros
Step 4 β Edge cases:
- All zeros: slow stays at 0, fill entire array with zeros
- No zeros: slow and fast march together, no changes needed
function moveZeros(array $arr): array {
$slow = 0;
// Move non-zeros to front
for ($fast = 0; $fast < count($arr); $fast++) {
if ($arr[$fast] !== 0) {
$arr[$slow] = $arr[$fast];
$slow++;
}
}
// Fill rest with zeros
while ($slow < count($arr)) {
$arr[$slow++] = 0;
}
return $arr;
}
print_r(moveZeros([0,1,0,3,12])); // [1,3,12,0,0]
Q8. Sort Array by Parity [E]
Question: Move even numbers to front, odd to back. In-place.
π§ How to Approach This
Step 1 β Recognize the pattern:
Partitioning array into two groups = Two Pointers (opposite direction), similar to Dutch National Flag.
Step 2 β Key insight:
Left pointer looks for the first ODD (misplaced on left side). Right pointer looks for the first EVEN (misplaced on right side). Swap them. Repeat.
Step 3 β Algorithm:
1. L=0, R=n-1
2. Advance L while arr[L] is even (already in place)
3. Advance R while arr[R] is odd (already in place)
4. If L < R: swap arr[L] and arr[R], L++, R--
Step 4 β Edge cases:
- All even: L walks all the way to end, no swaps needed
- All odd: R walks all the way to start, no swaps needed
function sortByParity(array $arr): array { $left = 0; $right = count($arr) - 1;
while ($left < $right) { while ($left < $right && $arr[$left] % 2 === 0) $left++; // even, skip while ($left < $right && $arr[$right] % 2 !== 0) $right--; // odd, skip if ($left < $right) { [$arr[$left], $arr[$right]] = [$arr[$right], $arr[$left]]; $left++; $right--; } } return $arr; }
print_r(sortByParity([3,1,2,4])); // [4,2,1,3] or [2,4,3,1]
---
## Two Pointers Pattern Summary
| Problem Pattern | Pointer Setup | Movement Rule |
|----------------|--------------|---------------|
| Pair sum in sorted | L=0, R=end | sum<target: L++; sum>target: R-- |
| Palindrome check | L=0, R=end | mismatch: return false; both move in |
| Remove duplicates | slow=0, fast=1 | fast!=slow: slow++, write |
| Partition (even/odd) | L=0, R=end | swap when wrong side |
| Move zeros | slow=0, fast=0 | non-zero: write to slow, slow++ |
| Merge sorted arrays | i=0, j=0 | pick smaller, advance that pointer |
| Container with water | L=0, R=end | move shorter side inward |
| Trap rainwater | L=0, R=end | process smaller side |