Two pointers is not one trick. It is three shapes.
Opposite ends, read/write, and fast/slow. Learn to see which one the problem wants and half the array questions become boring.
Contents
Every week someone messages me "bhaiya two pointers samajh aata hai but question mein apply nahi hota". That happens because people learn it as one trick. It is actually three different shapes, and the problem statement tells you which shape it wants if you read it slowly.
Shape 1: opposite ends, moving inward
Signal: the array is sorted (or you are allowed to sort it) and you want a pair with some property. Classic example: find two numbers in a sorted array that add up to target.
Brute force first, always. Two loops, check every pair. O(n²). For n = 10^5 that is 10^10 operations, TLE guaranteed.
Now the observation. If a[l] + a[r] is too small, moving r left can only make it smaller, so the only useful move is l++. If the sum is too big, the only useful move is r--. Every step throws away one element for good. That is why it is O(n).
int[] twoSum(int[] a, int target) {
int l = 0, r = a.length - 1;
while (l < r) {
int sum = a[l] + a[r];
if (sum == target) return new int[]{l, r};
if (sum < target) l++;
else r--;
}
return new int[]{-1, -1};
}
Let us dry run. a = [1, 3, 4, 6, 9], target = 10. l=0, r=4: 1 + 9 = 10, found. Change target to 7: 1 + 9 = 10 is too big, r=3. 1 + 6 = 7, found. Bas itna sa concept hai.
Same shape: container with most water, 3sum (fix one element, run two pointers on the rest), valid palindrome.
Shape 2: same direction, a slow writer and a fast reader
Signal: modify the array in place. Remove duplicates, move zeroes, partition, "keep the relative order". One pointer reads every element, the other points to where the next good element should be written.
int removeDuplicates(int[] a) {
if (a.length == 0) return 0;
int write = 1;
for (int read = 1; read < a.length; read++) {
if (a[read] != a[write - 1]) {
a[write++] = a[read];
}
}
return write;
}
Dry run on [1, 1, 2, 3, 3]. read=1 is a duplicate, skip. read=2 (value 2) differs from a[0], write it at index 1. read=3 (value 3) differs from a[1] = 2, write it at index 2. read=4 is a duplicate. Answer 3, and the array now starts with [1, 2, 3]. The reason this is safe: write never overtakes read, so you never overwrite something you still need.
Shape 3: fast and slow on a linked list
Signal: linked list, and the question is about a cycle, the middle, or the kth node from the end. Slow moves one step, fast moves two.
ListNode middle(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
When fast reaches the end, slow has covered half the distance. If there is a cycle, fast laps slow inside it and they meet (Floyd's algorithm). People memorise this one and forget why it works. Draw it on paper once with 6 nodes and you will never forget.
How to pick the shape in the interview
- Sorted + pair or triplet → opposite ends
- In place + "remove" / "move" / "keep relative order" → read/write
- Linked list + cycle / middle / kth from end → fast/slow
Homework: solve 3 questions of each shape. Then come back after 4 days and solve them again without looking. The second attempt is where the learning happens. Ghabrao mat, for most people it clicks around question 7 or 8.
Time: O(n) for all three shapes. Space: O(1).
Written by
Nikhil Sharan
I teach DSA and system design on YouTube (Two Pointers, 1.2M). Ex-SDE at two product companies, now teaching full time. Dry run everything. No paid course here, only free stuff and honest opinions.
Get the best of #desi_devs weekly
One email each Monday with the most useful posts. No spam; unsubscribe in one click.
Comments
Sign in to join the conversation.
Sign in to comment