The Quest Begins (The "Why")
I still remember the first time I stared at a coding interview problem that asked me to find two numbers in a sorted array that add up to a target. My brain went straight to the brute‑force playbook: two nested loops, O(n²), and a sinking feeling that I was about to get stuck in traffic while the interviewer’s clock ticked louder than a race engine. I kept thinking, “There’s gotta be a shortcut—something that feels like hitting the nitro boost instead of crawling in first gear.”
That moment sparked my quest for the two‑pointer technique. It’s not just another trick; it’s a mindset shift that turns a sluggish O(n²) slog into a sleek O(n) sprint. Once you see why the pointers can safely zip toward each other without missing any solution, the whole world of array‑based problems opens up like a neon‑lit highway at midnight.
The Revelation (The Insight)
Here’s the magic: when the array is sorted, the smallest element lives at the left end and the biggest at the right end. If you add them together and the sum is too small, you know you need a larger number—so you move the left pointer rightward to pick a bigger value. If the sum is too big, you need a smaller number—so you move the right pointer leftward. Because the array is monotonic, moving a pointer in the prescribed direction can never skip over a valid pair; you’re always discarding values that are provably useless for the current target.
Think of it like adjusting the heat on a stove: if the soup is too cold, you turn the knob up; if it’s too hot, you turn it down. You never overshoot the perfect temperature because you’re always moving in the direction that brings you closer. That monotonic guarantee is why two pointers give you a linear scan without any backtracking.
Wielding the Power (Code & Examples)
Problem 1: Two Sum II – Input array is sorted
LeetCode 167 – Given a 1‑indexed sorted array numbers, find two numbers that add up to target. Return their indices.
The struggle (brute force)
function twoSumBrute(numbers, target) {
for (let i = 0; i < numbers.length; i++) {
for (let j = i + 1; j < numbers.length; j++) {
if (numbers[i] + numbers[j] === target) return [i + 1, j + 1];
}
}
}
That’s O(n²) and feels like driving a sedan through a city full of stoplights—you’ll get there, but you’ll waste a lot of time.
The victory (two pointers)
function twoSum(numbers, target) {
let left = 0;
let right = numbers.length - 1;
while (left < right) {
const sum = numbers[left] + numbers[right];
if (sum === target) return [left + 1, right + 1]; // 1‑indexed
if (sum < target) {
left++; // need a bigger sum → move left pointer right
} else {
right--; // need a smaller sum → move right pointer left
}
}
}
Why it works: At each step we discard the impossibility that the current left (or right) could pair with any element beyond the opposite pointer because the array is sorted. The loop runs at most n iterations → O(n) time, O(1) space.
Common trap: Forgetting to adjust the pointers after a mismatch. If you only move one side when sum === target and leave the other unchanged on a miss, you’ll either infinite‑loop or miss the solution. Always shift the pointer that brings the sum closer to the target.
Problem 2: Remove Duplicates from Sorted Array
LeetCode 26 – Modify the array in‑place so each element appears only once and return the new length.
The struggle (extra space)
function removeDuplicatesExtra(nums) {
const seen = new Set();
let write = 0;
for (let num of nums) {
if (!seen.has(num)) {
seen.add(num);
nums[write++] = num;
}
}
return write;
}
That’s O(n) time but O(n) extra space—like carrying a spare tire everywhere when you could just patch the puncture.
The victory (two pointers)
function removeDuplicates(nums) {
if (nums.length === 0) return 0;
let slow = 0; // points to last unique element
for (let fast = 1; fast < nums.length; fast++) {
if (nums[fast] !== nums[slow]) {
slow++;
nums[slow] = nums[fast];
}
}
return slow + 1; // length of unique part
}
Why it works: The slow pointer always marks the end of the deduplicated prefix. Because the input is sorted, any new distinct value must appear after all duplicates of the previous value. When nums[fast] differs from nums[slow], we’ve found the next unique item and can safely copy it forward. Each element is inspected once → O(n) time, O(1) space.
Common trap: Starting slow at -1 and trying to write before the first element, or forgetting to return slow + 1. Keep the invariant clear: nums[0..slow] holds the unique values so far.
Why This New Power Matters
Mastering two pointers is like unlocking a secret nitro boost in your coding toolkit. Suddenly, problems that once felt like grinding through a mountain of nested loops become smooth highway cruises: Container With Most Water, Sort Colors (Dutch National Flag), Valid Palindrome, Intersection of Two Arrays II, and on and on. You’ll walk into interviews with the confidence of a driver who knows exactly when to shift gears and when to hit the gas.
The best part? The technique is portable. Once you internalize the monotonic reasoning—move the pointer that makes the outcome move toward the goal—you can adapt it to strings, linked lists, even matrix traversals. It’s less a specific algorithm and more a versatile pattern of thinking.
The Final Lap
I hope you feel the same rush I did when the two‑pointer click happened. It’s not just about writing fewer lines; it’s about seeing the hidden order in data and exploiting it with elegant, linear‑time solutions.
Your turn: Grab a sorted array problem you’ve struggled with before (maybe 3Sum Smaller or Maximum Subarray of Size K), smash it with two pointers, and share your solution in the comments. Let’s see who can push the nitro the farthest!
Happy coding, and may your pointers always find the sweet spot. 🚀
Top comments (0)