π― Two Pointers: Your Secret Superpower
Imagine youβre at a library. You need to find two books that together cost exactly $20. Would you check every single pair? That would take forever!
What if you had two helpersβone starting at the cheapest books, one at the most expensive? They walk toward each other, adjusting based on whether they need cheaper or pricier books.
Thatβs Two Pointers! Two markers moving through data, working together like a team.
πΆββοΈ The Two Pointers Technique
Whatβs the Big Idea?
Instead of checking every possible pair (super slow!), we use two arrows that move smartly through our list.
Think of it like this:
- You have a rope with beads
- One finger starts at the left end
- Another finger starts at the right end
- They move toward each other until they meet
When Do We Use It?
β Finding pairs that add up to something β Checking if something reads the same forwards and backwards β Finding the best container or area β Any problem with a sorted list and pairs!
Simple Example: Find Two Numbers That Add to 10
// Our sorted numbers
const nums = [1, 2, 4, 6, 8, 9];
const target = 10;
let left = 0; // Start pointer
let right = nums.length - 1; // End pointer
while (left < right) {
const sum = nums[left] + nums[right];
if (sum === target) {
console.log("Found:", nums[left], nums[right]);
break;
} else if (sum < target) {
left++; // Need bigger, move left forward
} else {
right--; // Need smaller, move right back
}
}
// Output: Found: 2 8
Why it works:
- List is sorted smallest to biggest
- If sum is too small β move left pointer right (get bigger number)
- If sum is too big β move right pointer left (get smaller number)
πΊ Three Sum Pattern
The Story
Now imagine you need three friends whose ages add up to 30. How do you find them quickly?
The trick: Pick one friend, then use two pointers to find the other two!
How It Works
- Sort the list
- Fix one number
- Use two pointers on the rest
- Move to next fixed number and repeat
graph TD A[Sort Array] --> B[Fix First Number] B --> C[Two Pointers on Rest] C --> D{Sum = Target?} D -->|Yes| E[Found Triplet!] D -->|Too Small| F[Move Left β] D -->|Too Big| G[Move Right β] F --> C G --> C
Example: Find Three Numbers That Add to Zero
function threeSum(nums) {
nums.sort((a, b) => a - b);
const result = [];
for (let i = 0; i < nums.length - 2; i++) {
// Skip duplicates
if (i > 0 && nums[i] === nums[i-1]) continue;
let left = i + 1;
let right = nums.length - 1;
while (left < right) {
const sum = nums[i] + nums[left] + nums[right];
if (sum === 0) {
result.push([nums[i], nums[left], nums[right]]);
left++;
right--;
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}
return result;
}
// Test
console.log(threeSum([-1, 0, 1, 2, -1, -4]));
// Output: [[-1, -1, 2], [-1, 0, 1]]
π Container With Most Water
The Story
You have walls of different heights. You want to fill water between two walls. Which two walls hold the most water?
Imagine: Fence posts of different heights. Which two posts, with a tarp between them, catch the most rain?
The Magic Formula
Water = Width Γ Shorter Wall Height
Height: 1 8 6 2 5 4 8 3 7
| β β | | β β
| β β β β β β β β
ββββββββββββββββββββββββ
Two Pointer Solution
Start pointers at both ends. Move the pointer at the shorter wall inward (hoping for a taller wall!).
function maxArea(heights) {
let left = 0;
let right = heights.length - 1;
let maxWater = 0;
while (left < right) {
const width = right - left;
const height = Math.min(heights[left], heights[right]);
const water = width * height;
maxWater = Math.max(maxWater, water);
// Move the shorter wall
if (heights[left] < heights[right]) {
left++;
} else {
right--;
}
}
return maxWater;
}
// Test
console.log(maxArea([1,8,6,2,5,4,8,3,7]));
// Output: 49 (between the two 8s)
Why move the shorter wall?
- Water is limited by the shorter wall
- Moving the taller wall can only make things worse (less width, same height limit)
- Moving the shorter wall might find something taller!
π Valid Palindrome
The Story
A palindrome reads the same forwards and backwards. Like βracecarβ or βlevelβ or βA man a plan a canal Panamaβ.
Think of a mirror: If you put a mirror in the middle, both sides should look identical!
Two Pointer Magic
One pointer starts at the beginning, one at the end. They walk toward each other, checking if letters match.
graph TD A[Start: left=0, right=end] --> B{Characters Match?} B -->|Yes| C[Move Both Inward] C --> D{Pointers Met?} D -->|No| B D -->|Yes| E[It's a Palindrome! β ] B -->|No| F[Not a Palindrome β]
Example Code
function isPalindrome(s) {
// Clean: only letters/numbers, lowercase
s = s.toLowerCase().replace(/[^a-z0-9]/g, '');
let left = 0;
let right = s.length - 1;
while (left < right) {
if (s[left] !== s[right]) {
return false; // Mismatch!
}
left++;
right--;
}
return true; // All matched!
}
// Tests
console.log(isPalindrome("racecar")); // true
console.log(isPalindrome("hello")); // false
console.log(isPalindrome("A man a plan")); // true
Why Two Pointers?
- Only need to check half the string
- Each comparison uses two characters at once
- Super fast: O(n) time!
π§οΈ Trapping Rain Water
The Story
This is the boss level of two pointers!
Imagine buildings in a row after rain. Water gets trapped between tall buildings. How much water is trapped?
Water trapped:
β
ββββββββ
βββββββββ
ββββββββββ
The β symbols are water!
The Key Insight
Water above any bar depends on:
- The tallest bar to its left
- The tallest bar to its right
- Water level = min(leftMax, rightMax) - current height
Two Pointer Approach
Instead of computing left/right max for each position, we use two pointers!
function trap(heights) {
let left = 0;
let right = heights.length - 1;
let leftMax = 0;
let rightMax = 0;
let water = 0;
while (left < right) {
if (heights[left] < heights[right]) {
// Left side is limiting factor
if (heights[left] >= leftMax) {
leftMax = heights[left];
} else {
water += leftMax - heights[left];
}
left++;
} else {
// Right side is limiting factor
if (heights[right] >= rightMax) {
rightMax = heights[right];
} else {
water += rightMax - heights[right];
}
right--;
}
}
return water;
}
// Test
console.log(trap([0,1,0,2,1,0,1,3,2,1,2,1]));
// Output: 6
Why Does This Work?
- We always process the side with the smaller current height
- For that side, we know the other side has an equal or taller bar
- So the limiting factor is definitely on our current side
- We can safely calculate water for that position!
π Summary: Your Two Pointer Toolkit
| Pattern | Setup | Movement Rule |
|---|---|---|
| Two Sum | Both ends | Move based on sum comparison |
| Three Sum | Fix one + both ends | Same as Two Sum, loop fixed |
| Container | Both ends | Move shorter wall |
| Palindrome | Both ends | Move both inward together |
| Rain Water | Both ends | Move smaller side |
π Golden Rules
- Sorted arrays often scream βTwo Pointers!β
- Start at ends for pair problems
- Move the limiting factor to find better solutions
- Skip duplicates when needed
- O(n) time is your reward!
π Youβve Got This!
Two Pointers is like having two fingers walking through your data. They work together, making smart moves based on what they see.
Remember:
- One slow day of checking everything = O(nΒ²) π«
- Two smart pointers = O(n) π
You now have a superpower that solves dozens of coding problems. Go practice and watch them fall like dominoes!