Two pointers means two indices that each only move one way. Opposite ends on a sorted array; fast and slow on a linked list; left/right walls that trap water; a candidate that eats everyone who cannot be celebrity. If the array is unsorted and you may not reorder it, the pair-sum sibling is a HashMap — that card belongs with hashing.
← Sliding Window · Intervals →
Opposite ends, then the list, then the walls
Simple Java shows the idea with fewer helpers. Interview Java is what you type: empty input, the Floyd phase-2 walk, O(1) rain, elimination plus verify. Celebrity variants stay unnamed until they confirm the classic sink.
Opposite-direction two-sum on a sorted array
Recognize it from
Array is sorted (or you may sort). Find two indices whose values sum to a target. Pair, not a subarray of length ≥ 3. Three-sum is this loop inside an outer index. If they forbid reordering and the array is unsorted, use a HashMap of complements instead.
What it actually does
lo starts at 0, hi at n − 1. The sum a[lo] + a[hi] is monotone in both pointers: too small means every remaining pair that still uses lo needs a larger partner, so lo++; too large means hi--. Each index moves at most once.
That argument needs sorted order. After a sort, original indices are lost unless you stored (value, index) pairs. Overflow: add in long in the interview block.
Simple — read this first
public boolean twoSumSorted(int[] a, int target) {
int lo = 0, hi = a.length - 1;
while (lo < hi) {
int s = a[lo] + a[hi];
if (s == target) return true;
// sorted: a larger left partner is the only way to grow the sum
if (s < target) lo++;
else hi--;
}
return false;
}
Interview — type this
public int[] twoSumSorted(int[] a, int target) {
if (a == null || a.length < 2) return new int[]{-1, -1};
int lo = 0, hi = a.length - 1;
while (lo < hi) {
long s = (long) a[lo] + a[hi]; // int+int can overflow past a 1e9 target
if (s == target) return new int[]{lo, hi};
if (s < target) lo++;
else hi--;
}
return new int[]{-1, -1};
}
Example 1 — simple
Input a = [1, 2, 4], target = 6.
| lo | hi | a[lo]+a[hi] | vs 6 | move |
|---|---|---|---|---|
| 0 | 2 | 1+4=5 | < | lo++ |
| 1 | 2 | 2+4=6 | = | found indices 1, 2 |
Answer true / indices [1, 2].
Example 2 — every step
Input a = [1, 2, 3, 4, 6], target = 10. Every lo/hi move, none skipped.
| lo | hi | sum | vs 10 | move |
|---|---|---|---|---|
| 0 | 4 | 1+6=7 | < | lo → 1 |
| 1 | 4 | 2+6=8 | < | lo → 2 |
| 2 | 4 | 3+6=9 | < | lo → 3 |
| 3 | 4 | 4+6=10 | = | stop, indices 3, 4 |
Answer indices [3, 4]. If the target were 11, the last step would be lo == hi after hi-- from a too-large 4+6, and you would return [-1, -1].
- Using
lo <= hiand pairing an element with itself. - Moving both pointers on a hit when they asked for any one pair.
- Running this loop on an unsorted array without sorting — the monotone argument dies.
Notebook problems for this topic
| Problem | Pattern | Links |
|---|---|---|
| Pair sum / sorted pair | Opposite ends if sorted; map if not | clue · clue |
| Array two-pointer (UNNAMED) + triangle DP | Lock their invariant; do not invent 3-sum | cheat card |
| Maximum sum linked list from two sorted lists | Two pointers on two lists; heavier segment between commons | Java in ch.05 · cheat card · R4 coding |
Fast / slow cycle detection
Recognize it from
Linked list, “does it loop,” “start of the cycle,” “happy number” (the tortoise on a function graph). Not a cycle in an explicit node-graph — that is DFS 3-state or Union-Find in the graphs chapter.
What it actually does
Slow walks one node, fast walks two. If there is no cycle, fast hits null. If there is a cycle, fast laps slow and they meet inside the cycle. That is phase 1. The simple method stops there and returns a boolean.
Phase 2 (interview): put one pointer back at the head and walk both one step at a time. They meet at the cycle entrance. Why: if the stem has length μ and they first met after the slow pointer had walked μ+a steps, the remaining distance around the cycle lines up so the head and the meet-point are equally far from the entrance. You do not need to prove it in Live Code — you do need to type the second walk and not forget fast.next.next null-checks.
Simple — read this first
public boolean hasCycle(ListNode head) {
if (head == null) return false;
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next; // one hop
fast = fast.next.next; // two hops; null-check is the no-cycle exit
if (slow == fast) return true;
}
return false;
}
Interview — type this
public ListNode detectCycle(ListNode head) {
if (head == null) return null;
ListNode slow = head, fast = head;
boolean met = false;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) { met = true; break; }
}
if (!met) return null;
// phase 2: head and meet-point are equally far from the cycle start
slow = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
Example 1 — simple
List 1 → 2 → 3 → 2 (3 points back at 2). Cycle of two nodes.
| move | slow | fast | meet? |
|---|---|---|---|
| start | 1 | 1 | not yet (loop has not moved) |
| 1 | 2 | 3 | no |
| 2 | 3 | 3 | yes — hasCycle |
Answer true. Interview phase 2 from this meet: reset slow to 1, fast stays at 3. One step: slow=2, fast=2. Cycle start is 2.
Example 2 — every step
List 1 → 2 → 3 → 4 → 5 → 3. Meet point, then the start-of-cycle walk.
| move | slow | fast | fast path |
|---|---|---|---|
| start | 1 | 1 | — |
| 1 | 2 | 3 | 1→2→3 |
| 2 | 3 | 5 | 3→4→5 |
| 3 | 4 | 4 | 5→3→4 — meet at 4 |
| move | slow (from head) | fast (from meet) | equal? |
|---|---|---|---|
| reset | 1 | 4 | no |
| 1 | 2 | 5 | no |
| 2 | 3 | 3 | yes — cycle starts at 3 |
Answer: cycle starts at node 3. Simple hasCycle would have returned true at the meet on move 3 and stopped.
HashSet of nodes is O(n) extra memory and is the correct brute if they allow it. Fast/slow uses the list itself as the visited structure. Marking val is cheating unless they said you may mutate.
- Checking
slow == fastbefore the first move, so you “meet” at the head of a list that has no cycle. - Writing
fast.next.nextwithoutfast.next != null. - Returning the meet node as the cycle start — that is only true for some μ. Phase 2 is required.
Notebook problems for this topic
| Problem | Pattern | Links |
|---|---|---|
| Linked-list cycle / cycle start | Fast/slow, then optional phase 2 | Standard CS — no named Job-10454435 IE hash in ch.05 / 24 / R4. Chapter 11 continues dummy-head list work. |
Trapping rain water — two pointers
Recognize it from
Bars on a line, water sits in valleys. Water at index i is min(leftMax, rightMax) − h[i] if that is positive. Container-with-most-water is the sibling (area between two bars, not water on bars). This card is trapping.
What it actually does
The simple method precomputes, for every i, the tallest bar to the left and to the right (including i). Then one pass adds min(L[i], R[i]) − h[i]. That is O(n) time and O(n) extra arrays.
The interview method keeps two pointers and running maxL / maxR. The smaller wall decides the water at that pointer: if h[left] ≤ h[right], the left bar’s water cannot exceed maxL, because there is already a right wall at least as tall as h[left]. Update maxL or add water, then move left. Symmetric on the right. O(1) extra memory.
Simple — read this first
public int trap(int[] h) {
int n = h.length;
if (n == 0) return 0;
int[] leftMax = new int[n], rightMax = new int[n];
leftMax[0] = h[0];
for (int i = 1; i < n; i++) leftMax[i] = Math.max(leftMax[i - 1], h[i]);
rightMax[n - 1] = h[n - 1];
for (int i = n - 2; i >= 0; i--) rightMax[i] = Math.max(rightMax[i + 1], h[i]);
int ans = 0;
for (int i = 0; i < n; i++) {
// valley depth = shorter bounding wall minus this bar
ans += Math.min(leftMax[i], rightMax[i]) - h[i];
}
return ans;
}
Interview — type this
public int trap(int[] h) {
if (h == null || h.length == 0) return 0;
int left = 0, right = h.length - 1;
int maxL = 0, maxR = 0, ans = 0;
while (left < right) {
if (h[left] <= h[right]) {
// right is already a wall ≥ this bar, so left water is bounded by maxL
if (h[left] >= maxL) maxL = h[left];
else ans += maxL - h[left];
left++;
} else {
if (h[right] >= maxR) maxR = h[right];
else ans += maxR - h[right];
right--;
}
}
return ans;
}
Example 1 — simple
Input h = [0, 1, 0, 2]. Four bars, one unit of water.
| i | h[i] | leftMax | rightMax | water |
|---|---|---|---|---|
| 0 | 0 | 0 | 2 | 0 |
| 1 | 1 | 1 | 2 | 0 |
| 2 | 0 | 1 | 2 | 1 |
| 3 | 2 | 2 | 2 | 0 |
Answer 1.
Example 2 — every step
Input h = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1] — the classic 12-bar, every pointer move, no bar skipped.
| left | right | h[L], h[R] | side | maxL | maxR | water added | ans |
|---|---|---|---|---|---|---|---|
| 0 | 11 | 0, 1 | L: new maxL=0 | 0 | 0 | 0 | 0 |
| 1 | 11 | 1, 1 | L: new maxL=1 | 1 | 0 | 0 | 0 |
| 2 | 11 | 0, 1 | L: 1−0 | 1 | 0 | 1 | 1 |
| 3 | 11 | 2, 1 | R: new maxR=1 | 1 | 1 | 0 | 1 |
| 3 | 10 | 2, 2 | L: new maxL=2 | 2 | 1 | 0 | 1 |
| 4 | 10 | 1, 2 | L: 2−1 | 2 | 1 | 1 | 2 |
| 5 | 10 | 0, 2 | L: 2−0 | 2 | 1 | 2 | 4 |
| 6 | 10 | 1, 2 | L: 2−1 | 2 | 1 | 1 | 5 |
| 7 | 10 | 3, 2 | R: new maxR=2 | 2 | 2 | 0 | 5 |
| 7 | 9 | 3, 1 | R: 2−1 | 2 | 2 | 1 | 6 |
| 7 | 8 | 3, 2 | R: maxR stays 2 | 2 | 2 | 0 | 6 |
| 7 | 7 | — | stop left==right | 2 | 2 | — | 6 |
Answer 6. Bar 5 (height 0) is the 2-unit puddle between the 2-wall and the 3-wall; the last unit sits at index 9.
- Using
maxof the two walls instead ofmin. - Moving both pointers in one iteration.
- Coding container-with-most-water (area
min(h[l],h[r]) * (r−l)) when they asked for trapped units.
Notebook problems for this topic
| Problem | Pattern | Links |
|---|---|---|
| Trapping rainwater; stocks DP on the same IE | Lmax/Rmax or two pointers; stocks is a different state DP | cheat card |
Celebrity elimination
Recognize it from
A celebrity is known by everyone and knows no one. API knows(a, b) or a 0/1 matrix. Ask: none possible (−1)? n=1? directed “follows”? Variants stay unnamed — confirm classic before you type this elimination.
What it actually does
Brute: for each candidate, scan the whole row (knows nobody) and column (everybody knows them). O(n²) calls.
Interview: one candidate. For i from 1 to n−1, if knows(cand, i) then cand is not a celebrity, so cand = i; else i is not. One person remains. Then verify in O(n): cand knows nobody, everybody else knows cand. Skip verify and you ship a false celebrity. Two-pointer form is the same idea: if A knows B, A dies, else B dies.
Simple — read this first
public int findCelebrity(int n) {
for (int cand = 0; cand < n; cand++) {
boolean ok = true;
for (int i = 0; i < n; i++) {
if (i == cand) continue;
// celebrity knows nobody, and everybody knows celebrity
if (knows(cand, i) || !knows(i, cand)) { ok = false; break; }
}
if (ok) return cand;
}
return -1;
}
Interview — type this
public int findCelebrity(int n) {
if (n <= 0) return -1;
int cand = 0;
for (int i = 1; i < n; i++) {
// if cand knows i, cand is not the sink; else i is not
if (knows(cand, i)) cand = i;
}
for (int i = 0; i < n; i++) {
if (i == cand) continue;
if (knows(cand, i) || !knows(i, cand)) return -1;
}
return cand;
}
boolean knows(int a, int b) { return false; // interviewer oracle }
Example 1 — simple
n=3, person 1 is known by everyone and knows no one.
| step | cand | query | result | next cand |
|---|---|---|---|---|
| init | 0 | — | — | 0 |
| i=1 | 0 | knows(0,1) | true | 1 |
| i=2 | 1 | knows(1,2) | false | 1 |
| verify i=0 | 1 | knows(1,0)=F, knows(0,1)=T | ok | 1 |
| verify i=2 | 1 | knows(1,2)=F, knows(2,1)=T | ok | return 1 |
Answer 1.
Example 2 — every step
n=3, no celebrity. Elimination still picks someone; verify fails.
| step | cand | query | result | next |
|---|---|---|---|---|
| init | 0 | — | — | 0 |
| i=1 | 0 | knows(0,1) | true | cand=1 |
| i=2 | 1 | knows(1,2) | false | cand stays 1 |
| verify i=0 | 1 | knows(1,0) | true — cand knows someone | return −1 |
Answer −1. Person 1 survived elimination and still knows 0. Skipping verify would have returned 1.
knows(a,b) true → a is not celebrity. False → b is not. You discard n−1 people, then spend n more queries on the survivor. Graph SCC is the wrong tool unless they changed the definition.
- Returning the candidate without the verify loop.
- Checking only “everyone knows cand” and forgetting “cand knows nobody.”
- Stamping a LeetCode id on an unnamed variant. Ask the oracle vs matrix, and whether none is allowed.
Notebook problems for this topic
| Problem | Pattern | Links |
|---|---|---|
| Celebrity (classic / unnamed variant) | Eliminate one per knows() call, then verify | Java in ch.05 · cheat card · clue · R4 coding |
See also on this chapter
Max-sum switching lists is two pointers on two sorted lists, not opposite-ends on one array — linked from the two-sum card. Rain-stocks on chapter 24 is rain plus a stocks DP; only the rain half is this chapter. tp-tri’s array problem is UNNAMED: lock the invariant, then this opposite-ends loop if they confirmed sorted pair-sum.