LC Patterns — Two / Three / N Pointers

Previous: binary search
Per-pattern template: What it is → Intuition → Approach → Challenges & pitfalls → LC problems (max 3) → Worked examples.
Deliberately excluded: sliding window (variable-size subarray/substring problems) — it's technically two same-direction pointers, but it has its own rich pattern family and gets its own file (sliding window).

The Master Insight (read before the patterns)

Checking every pair in an array is O(n²). Two pointers cuts that to O(n) by using order (the array is sorted, or has some other one-directional structure): each pointer only ever moves one way, so together they move at most 2n steps — and the structure guarantees that the pairs you skip could never have been the answer.

What you must be able to say in every two-pointer solution: one sentence for why moving this pointer can't skip a valid answer — a "this side is used up" argument ("anything left could still pair with is even smaller, so left is done"). If you can't say that sentence, you don't have a two-pointer solution — you have two indices and a hope. Interviewers poke exactly here.

There are only three ways the pointers can move; every pattern below is one of them:

  1. Converge — start at both ends, move toward each other (finding a pair, partitioning by comparison).
  2. Chase — both start on the left and move the same direction at different speeds/conditions (reader/writer, fast/slow, cycles).
  3. Parallel — one pointer per sequence, advance whichever you just consumed (merging, matching, subsequence checks).

"Three/N pointers" is never a new idea: it's either an outer loop pinning one element with a two-pointer converge inside (k-sum), or three chase pointers keeping a partition tidy (Dutch flag).

§0Templates & Universal Pitfalls

Template 1 — Converge

left, right = 0, n - 1
while left < right:
    if condition(left, right):   # e.g., sum == target → found / record
        ...
    elif too_small(...):  left += 1     # only the limiting side moves
    else:                 right -= 1

Template 2 — Chase (reader/writer form)

write = 0
for read in range(n):            # read always advances; write only on keep
    if keep(nums[read]):
        nums[write] = nums[read]
        write += 1
return write                     # new logical length

Template 3 — Parallel

i = j = 0
while i < len(A) and j < len(B):
    if A[i] <= B[j]: consume A[i]; i += 1
    else:            consume B[j]; j += 1
# drain the leftover of whichever remains

Universal pitfalls

  1. Moving the wrong pointer (or both at once) without a reason — the classic silent wrong answer. Decide which pointer is provably used up and move only that one.
  2. Duplicate handling in k-sum: skip duplicates at every pointer level (while nums[i] == nums[i-1]: i += 1) — and only after recording a hit, or you'll skip the valid first occurrence.
  3. left < right vs left <= right: finding a pair needs < (an element can't pair with itself); single-element checks (palindrome center) can use <=. Get this wrong and you either loop forever or miss the middle.
  4. Forgetting to drain the leftover tail in parallel/merge patterns.
  5. Sorting when you mustn't: if the answer needs the original indices (LC 1 Two Sum), sorting throws them away — use a hash map instead. Say the trade-off out loud: two pointers cost O(n log n) to sort but O(1) space; a hash map is O(n) time and space.
  6. Linked-list null-safety: every fast.next.next needs fast and fast.next in the loop guard — recite the guard before you write the loop.

1Converging Pointers on a Sorted Array (pair with target)

What it is: The array is sorted (or you sort it), and you want a pair with some property — a sum equal to a target, or a mirror check like a palindrome. Put one pointer at each end; each comparison tells you which side is "too small" or "too big," and that side steps inward.

Intuition: Say sum = arr[l] + arr[r]. If it's too small, then no pair using arr[l] can ever reach the target — every partner still available is ≤ arr[r], and that already wasn't enough. So arr[l] is used up: move l right. Too big is the mirror case: move r left. Every step throws away one element for good → O(n) after sorting. It's the same "prune what can't help" idea as binary search, but for pairs.

Approach: Template 1. For palindrome checks, the condition is s[l] == s[r], and both pointers move on a match. For strings with junk characters (non-alphanumerics), skip past those inside the loop.

Challenges & pitfalls

saying the "this side is used up" argument out loud (see master insight); < vs <= in the loop; the sorted precondition — if the input isn't sorted and indices matter, this pattern doesn't apply (pitfall 5).

LC 167 Two Sum II — SortedLC 125 Valid PalindromeLC 344 Reverse String
Example 1: Two Sum II (LC 167) — sorted numbers = [2, 7, 11, 15], target 9. Return the two 1-based indices.
  • l = 0 (2), r = 3 (15): sum 17 > 9 → too big → move r left.
  • l = 0 (2), r = 2 (11): sum 13 > 9 → too big → move r left.
  • l = 0 (2), r = 1 (7): sum 9 → found! Return [1, 2] (1-indexed). ✓ Each step permanently ruled out the biggest remaining value.
Example 2: Valid Palindrome (LC 125) — is "aba" a palindrome?
  • l = 0 ('a'), r = 2 ('a'): match → step both inward.
  • Now l = 1, r = 1 — pointers met, loop ends. Every compared pair matched → true. ✓ (For "abca": a==a, then b vs c mismatch → false.)

2Converging with a Greedy Invariant (move the limiting side)

What it is: Both ends still move inward, but the rule isn't a sorted sum — it's a bottleneck: the answer at any (l, r) is capped by the weaker side (shorter line, lower wall), so moving the stronger side can't possibly help, and you always advance the weaker one.

Intuition: Container With Most Water — the area is width × min(h[l], h[r]). If you move the taller side inward, the width shrinks and the height is still capped by the shorter side, so the area can only get worse. So the shorter side is the one holding you back: move it, hoping to find a taller wall. Trapping Rain Water extends this with running maxima: whichever side has the smaller max so far is fully decided ("water here = maxLeft − height"), so lock it in and advance it.

Approach: Template 1, where the move rule is "advance the pointer with the smaller height/value," usually while carrying best, max_left, max_right accumulators. For pairing problems (boats): greedily put the heaviest with the lightest; if they don't fit together, the heaviest goes alone.

Challenges & pitfalls

these need a real proof, not a vibe — rehearse the exchange argument for 11 and the "smaller-max side is decided" claim for 42 until each is two clean sentences; accumulator updates in the wrong order (update the max before or after computing water? trace one example); it's easy to hand-wave and get caught in the follow-up.

LC 11 Container With Most WaterLC 42 Trapping Rain WaterLC 881 Boats to Save People
Example 1: Container With Most Water (LC 11)height = [1, 8, 6, 2, 5, 4, 8, 3, 7].
  • l = 0 (1), r = 8 (7): area = min(1, 7) × 8 = 8. The left wall (1) is shorter → move l.
  • l = 1 (8), r = 8 (7): area = min(8, 7) × 7 = 49. Best so far 49. Now the right wall (7) is shorter → move r.
  • Keep going; nothing beats 49 (every later width is smaller and no pair of tall walls is far enough apart). Answer 49. ✓
  • Why moving the shorter side is safe: keeping the shorter wall and moving the taller one in only shrinks the width while the height stays capped — it can never improve.
Example 2: Trapping Rain Water (LC 42)height = [0,1,0,2,1,0,1,3,2,1,2,1], total trapped = 6.
  • Track maxLeft and maxRight. Whichever side's running max is smaller is "decided": the water above that bar is smallerMax − height.
  • Walking with that rule sums the trapped water bar by bar to 6. The key sentence: if maxLeft < maxRight, the left bar's water depends only on maxLeft (the right side already guarantees a taller wall), so you can commit it and move left. ✓

3K-Sum Family (pin + converge; the "three/N pointers" classic)

What it is: Find triplets/quadruplets that sum to a target: sort the array, pin the first element with an outer loop, and solve the remaining pair with Pattern 1's converge inside. 4Sum pins two elements, kSum recurses down to 2Sum — so "N pointers" is really "N−2 pinned loops + one converge."

Intuition: Sorting buys two things at once: the two-pointer sum walk and easy duplicate-skipping (equal values sit next to each other). The recursion: kSum(target) = for each pin i, solve (k−1)Sum(target − nums[i]) — the base case 2Sum is the only place pointers actually move.

Approach: sort → for i (skip dups: if i > 0 and nums[i] == nums[i-1]: continue) → converge l = i+1, r = n-1 → on a hit, record it and skip duplicates on both l and r, then move both. Prune: break when nums[i] * k > target (even the smallest possible sum is already too big). O(n^(k−1)).

Challenges & pitfalls

skipping duplicates at every level without skipping a legitimate combination (skip only relative to the previous same-level value); on a hit, move both pointers, not one; overflow in 4Sum for large values (Java/C++); "3Sum Closest" drops the dedup and tracks best-distance instead — don't blindly copy the exact-match skeleton.

LC 15 3SumLC 16 3Sum ClosestLC 18 4Sum
Example 1: 3Sum (LC 15)nums = [-1, 0, 1, 2, -1, -4], find all triplets summing to 0.
  • Sort first → [-4, -1, -1, 0, 1, 2].
  • Pin -4 (need a pair summing to 4): l=1(-1), r=5(2) → −1+2 = 1, too small, and even the biggest remaining pair can't reach 4 → no triplet here.
  • Pin -1 (index 1, need pair summing to 1): l=2(-1), r=5(2) → sum 1 → triplet [-1, -1, 2]. Then l=3(0), r=4(1) → sum 1 → triplet [-1, 0, 1].
  • Pin -1 again (index 2): same value as the previous pin → skip, to avoid duplicate triplets.
  • Pin 0 (need pair summing to 0): l=4(1), r=5(2) → sum 3, too big, no smaller pair left → done.
  • Answer [[-1, -1, 2], [-1, 0, 1]]. ✓ Sorting made both the pair-walk and the dedup trivial.

4Reader/Writer (in-place compaction)

What it is: Remove or rearrange elements in place with O(1) extra space: one pointer reads every element, a second (always ≤ the reader) marks where the next "kept" element goes. Everything before the writer is the answer-so-far; the function returns the writer as the new length.

Intuition: The invariant does all the work: [0, write) is exactly the kept region, [write, read) is garbage you're free to overwrite. The reader advances every step, the writer only on keeps — so the writer never overtakes the reader and no kept data is ever clobbered. This is compaction, the same trick garbage collectors and log compaction use.

Approach: Template 2. Variants: "at most k duplicates" → keep if nums[read] != nums[write-k]; "move zeroes preserving order" → write the non-zeros, then either fill the tail with zeros or swap-on-write to keep the values.

Challenges & pitfalls

stating the invariant before coding (it's also exactly what the interviewer wants to hear); the write-k back-reference for at-most-k variants (compare against the written region, not the read region); stability (move-zeroes must preserve order — a swap-from-both-ends approach breaks it).

LC 26 Remove Duplicates from Sorted ArrayLC 27 Remove ElementLC 283 Move Zeroes
Example 1: Remove Duplicates from Sorted Array (LC 26)nums = [0, 0, 1, 1, 2, 3, 3], keep one of each, in place.
  • Keep index 0 as-is; write = 1. Reader walks from index 1, and we write only when the value differs from the last kept one (nums[write-1]).
  • read 0 (= last kept 0) → skip. read 1 (≠ 0) → write at index 1, write = 2. read 1 → skip. read 2 (≠ 1) → write at index 2, write = 3. read 3 (≠ 2) → write at index 3, write = 4. read 3 → skip.
  • Front of the array is now [0, 1, 2, 3, ...], return length 4. ✓ Everything before write is the deduped answer; the tail is leftover junk we ignore.
Example 2: Move Zeroes (LC 283)nums = [0, 1, 0, 3, 12], push zeros to the end, keep order.
  • Writer marks where the next non-zero goes. Walk: read 0 skip; read 1 → write at 0; read 0 skip; read 3 → write at 1; read 12 → write at 2.
  • Now [1, 3, 12, 3, 12]; fill the tail (indices 2+... i.e. from write) with zeros → [1, 3, 12, 0, 0]. ✓ Order of the non-zeros is preserved because the writer copies them left in the order it sees them.

5Fast & Slow: Cycle Detection (Floyd)

What it is: Two pointers race through a linked structure — slow moves 1 step, fast moves 2. If there's a cycle they must meet inside it; if not, fast runs off the end (null). A second phase (reset one pointer to the head, move both at speed 1) finds the exact spot where the cycle begins.

Intuition: Inside a cycle, fast gains exactly 1 node on slow every step, so it can't jump over slow — they're guaranteed to land on the same node within one lap. Phase 2 is a little algebra: at the meeting point, the distance from the head to the cycle start equals the distance from the meeting point to the cycle start (2(a+b) = a+b+c+b ⇒ a = c) — memorize that two-line derivation, interviewers ask for it. The big generalization: any repeated function x → f(x) on a finite set is secretly a linked list — that's how 287 (the array is i → nums[i]) and happy-number turn into cycle problems.

Approach: while fast and fast.next: → advance; if slow == fast: cycle found. Then reset one pointer to the head and walk both at speed 1 until they meet — that's the entry. For 287: f(i) = nums[i], and the duplicate value is the cycle entry.

Challenges & pitfalls

the loop guard (pitfall 6); start both at the head and check equality after moving (checking before you move gives a false positive); recognizing the disguised versions (287 is the real test — the array is the linked list); explaining why they must meet without waving hands ("gains 1 per step, so the gap shrinks to 0").

LC 141 Linked List CycleLC 142 Linked List Cycle IILC 287 Find the Duplicate Number
Example 1: Find the Duplicate Number (LC 287)nums = [1, 3, 4, 2, 2]. Treat each index as a node pointing to nums[index]; the repeated value is where two arrows collide → a cycle entrance.
  • Phase 1 (slow +1, fast +2), starting from index 0:
    • slow: 0 → nums[0]=1nums[1]=3nums[3]=2nums[2]=4
    • fast: 0 → nums[1]=3nums[2]=4nums[2]=4nums[2]=4
    • They meet at value 4.
  • Phase 2: reset one pointer to index 0, move both by 1:
    • ptr: 0 → 1 → 3 → 2; other: 4 → 2 → 4 → 2. They meet at 2.
  • The duplicate is 2. ✓ No extra array, no modifying the input — the cycle math found it.
Example 2: Linked List Cycle (LC 141) — list 1 → 2 → 3 → 4 → 2 (the 4 points back to 2).
  • slow steps 1→2→3→4→2…, fast steps 1→3→2→4→3…; because fast closes the gap by one node each step, they eventually land on the same node → cycle exists. If instead fast ever reaches null, there's no cycle. ✓

6Linked-List Positioning (gap-K and midpoint)

What it is: Find a spot in a singly linked list you can't index into — the middle, or the k-th from the end — in a single pass. Either give one pointer a k-node head start (then move both until the leader hits the end), or use speeds 1 and 2 so the slow pointer lands in the middle when fast finishes.

Intuition: A fixed head start is preserved as both pointers move at the same speed — so when the leader reaches the end, the trailer is exactly k behind it, no length count needed. Speed 2-vs-1 means slow covers half of whatever fast covers: fast at the end ⇒ slow at the middle. These combine into recipes: "is this list a palindrome" = find the middle (this pattern) + reverse the second half + compare in parallel (Pattern 7).

Approach: gap-K: advance lead k steps (often from a dummy node, so "delete the k-th from end" leaves you just before the target); then move both to the end. Midpoint: while fast and fast.next: slow = slow.next; fast = fast.next.next.

Challenges & pitfalls

the even-length middle convention (with the guard above, slow lands on the second of the middle pair — check it against the problem's definition on a 4-node example); deleting requires stopping one node before (a dummy node is the standard fix — use it by default and say why); restoring the list if you reversed half (interviewers may require it).

LC 19 Remove Nth Node From EndLC 876 Middle of the Linked ListLC 234 Palindrome Linked List
Example 1: Remove Nth Node From End (LC 19) — list 1 → 2 → 3 → 4 → 5, remove the 2nd from the end (node 4).
  • Put a dummy before the head: dummy → 1 → 2 → 3 → 4 → 5. Give lead a head start of n + 1 = 3 steps from the dummy: lead now points at node 3. trail starts at the dummy.
  • Move both one at a time until lead falls off the end:
    • lead 3, trail dummy → lead 4, trail 1 → lead 5, trail 2 → lead null, trail 3.
  • trail sits just before the target, so trail.next = trail.next.next splices out node 4 → 1 → 2 → 3 → 5. ✓ The head start of exactly n+1 is what leaves trail one node early.
Example 2: Middle of the Linked List (LC 876) — list 1 → 2 → 3 → 4 → 5.
  • slow +1, fast +2: slow 1→2→3, fast 1→3→5(end). slow stops at 3, the middle. ✓ For an even list 1→2→3→4, slow lands on 3 (the second of the two middles) with this guard.

7Parallel Pointers / Merging Two Sequences

What it is: One pointer per sequence; at each step, compare the two current elements and advance the pointer whose element you just used. This is the merge step of merge-sort, generalized: merging, intersecting, or comparing two sorted (or independently walkable) sequences in O(m+n).

Intuition: Sortedness means the smaller current element can't match or beat anything further along in the other sequence — so use it now and move on. The famous twist for in-place merge (88): merge backwards, largest first — the free space is at the end of the target array, so writing the biggest values there never overwrites an element you haven't read yet.

Approach: Template 3 + drain the leftover. Variants: intersection (advance the smaller, record equals), backspace-compare (walk both backwards, resolving #s with a skip counter before each comparison — going backwards turns "look ahead" into "look behind").

Challenges & pitfalls

forgetting to drain (pitfall 4); merging in place forwards (overwrites A — backwards is the whole trick; if you start forwards in an interview, recover with "wait — the free space is at the end, I should fill from the back"); duplicate policy in intersections (350 keeps counts, 349 doesn't).

LC 88 Merge Sorted ArrayLC 21 Merge Two Sorted ListsLC 844 Backspace String Compare
Example 1: Merge Sorted Array (LC 88)nums1 = [1, 2, 3, 0, 0, 0] (first m=3 are real), nums2 = [2, 5, 6]. Merge into nums1 in place.
  • Fill from the back. Three pointers: i=2 (last real of nums1 = 3), j=2 (last of nums2 = 6), k=5 (last slot of nums1).
    • 6 > 3 → put 6 at slot 5. j=1, k=4.
    • 5 > 3 → put 5 at slot 4. j=0, k=3.
    • 3 > 2 → put 3 at slot 3. i=1, k=2.
    • nums1[1]=2 vs nums2[0]=2 → put the nums2 one at slot 2. j=-1, k=1.
    • nums2 exhausted; nums1's own [1, 2] are already in place.
  • Result [1, 2, 2, 3, 5, 6]. ✓ Filling largest-first from the end means we never step on an unread value.

8Expand From Center (diverging pointers)

What it is: The opposite of converge: the pointers start together and move outward. Every palindrome has a center, so try all 2n−1 centers (each character, and each gap between two characters) and expand l--, r++ while the characters match.

Intuition: A palindrome is fully described by its center + how far it reaches, so listing every center covers every palindrome exactly once, and expansion stops at the first mismatch (you get the longest one for free). 2n−1 centers × O(n) expansion = O(n²) with O(1) space — the sweet spot between brute force O(n³) and Manacher's O(n) (name-drop Manacher; never code it live).

Approach: for each i: expand from (i, i) (odd-length) and from (i, i+1) (even-length); track the best window, or count every successful expansion (647 counts each expansion step, not just the maximal one).

Challenges & pitfalls

forgetting the even centers (the #1 bug — "aa" has no single-char center); off-by-one when turning the final (l, r) into a substring (after the failed step, the answer is s[l+1:r] — trace it once); knowing when DP beats this (when later stages need all palindromic substructure, e.g. partitioning).

LC 5 Longest Palindromic SubstringLC 647 Palindromic Substrings
Example 1: Longest Palindromic Substring (LC 5)s = "babad".
  • Odd center at index 1 ('a'): expand → s[0]='b' and s[2]='b' match → try to go further, but l falls off the left end → stop. Palindrome "bab", length 3.
  • Odd center at index 2 ('b'): s[1]='a', s[3]='a' match → next s[0]='b', s[4]='d' mismatch → stop. Palindrome "aba", length 3.
  • Longest is length 3 ("bab""aba" ties, either is accepted). ✓
  • Even center example, s = "cbbd": center between indices 1 and 2 → s[1]='b', s[2]='b' match → answer "bb". This is the case you lose if you forget even centers.

9Dutch National Flag (three-way partition, three chase pointers)

What it is: Split an array into three regions in one pass with three pointers: low (edge of the 0-region), mid (the scanner), high (edge of the 2-region). Classic form: sort an array of 0s/1s/2s in place, without counting.

Intuition: Keep four zones by invariant: [0, low) = 0s, [low, mid) = 1s, [mid, high] = unknown, (high, n) = 2s. Every scanner step shrinks the unknown zone by at least one, from one side or the other → O(n), one pass. This is quicksort's 3-way partition; knowing that connection answers "where would you use this in real life?" (sorting data with lots of equal keys).

Approach: mid scans: on 0 → swap into low, advance both; on 1 → just mid++; on 2 → swap into high, high--, and do not advance mid (the element that just arrived from high hasn't been examined yet). Two-way (parity/binary) partitions are the degenerate case (simple reader/writer or converge-swap).

Challenges & pitfalls

the don't-advance-mid-after-a-high-swap rule (the bug everyone writes — the element coming from high is unknown, while the one coming from low is a known 1, hence the asymmetry — be ready to explain why); the loop condition is mid <= high (not <); generalizing to "partition by a pivot into <, =, >".

LC 75 Sort ColorsLC 905 Sort Array by ParityLC 922 Sort Array by Parity II
Example 1: Sort Colors (LC 75)nums = [2, 0, 2, 1, 1, 0], sort 0s/1s/2s in place. Start low=0, mid=0, high=5.
  • mid=0 sees 2 → swap with high(5): [0, 0, 2, 1, 1, 2], high=4, mid stays (must re-check the swapped-in value).
  • mid=0 sees 0 → swap with low(0) (itself): low=1, mid=1.
  • mid=1 sees 0 → swap with low(1) (itself): low=2, mid=2.
  • mid=2 sees 2 → swap with high(4): [0, 0, 1, 1, 2, 2], high=3, mid stays.
  • mid=2 sees 1 → just mid=3.
  • mid=3 sees 1mid=4. Now mid > high (4 > 3) → stop.
  • Result [0, 0, 1, 1, 2, 2]. ✓ The one subtlety: after a high-swap we don't advance mid, because that new value is still unexamined.

10Subsequence Matching Across Two Strings

What it is: Check whether string s is a subsequence of t (same characters in order, gaps allowed): one greedy pointer per string, always advance t's pointer, advance s's only on a match; if s runs out, it's a subsequence. Extends to "which of many candidate words fit inside t."

Intuition: Greedy matching is safe by an exchange argument: matching each character of s at the earliest place it can go in t never hurts — any later match could be swapped for the earlier one. This is Pattern 7's parallel topology, where one sequence is the filter and only one pointer's advance is conditional.

Approach: two pointers, O(m+n) per check. The follow-up that levels you up (asked in 392): many queries against the same t → preprocess t into "char → sorted list of positions," then each query character binary-searches the next usable position (binary search Pattern 2 — the series composes). For 524, run the check per candidate, tracking longest-then-lexicographically-smallest.

Challenges & pitfalls

the multi-query follow-up (know the preprocessing answer cold — it's the real interview question hiding in 392); tie-breaking in 524 (length first, then lexicographic order — read the statement precisely); resisting DP/regex overkill for what is a linear greedy scan.

LC 392 Is SubsequenceLC 524 Longest Word in Dictionary through Deleting
Example 1: Is Subsequence (LC 392) — is s = "abc" a subsequence of t = "ahbgdc"?
  • Pointer i into s, walk t left to right:
    • looking for a: t[0]='a' matches → ib.
    • looking for b: t[1]='h' no, t[2]='b' yes → ic.
    • looking for c: t[3]='g' no, t[4]='d' no, t[5]='c' yes → i runs off the end of s.
  • s fully matched → true. ✓ Grabbing each letter at its earliest spot in t is always safe.
Example 2: many queries (LC 392 follow-up) — if you must test thousands of different s against the same t, don't re-scan t each time. Precompute, for t = "ahbgdc": a→[0], h→[1], b→[2], g→[3], d→[4], c→[5]. Then for each query letter, binary-search the position list for the smallest index greater than where you are — turning each check into O(|s| log |t|). ✓

Summary Table — Recognition Cheat Sheet

#PatternTopologyTrigger phrase in problemKey invariant/argument
1Sorted pair convergeConverge"sorted", "pair summing to", "palindrome check"too-small side is used up
2Greedy invariant convergeConverge"container", "trap water", "pair heaviest/lightest"bottleneck side is decided — move it
3K-sumPin + converge"all triplets/quadruplets summing to"sort + dedup at every level
4Reader/writerChase"remove in place", "O(1) extra space"[0,write) = kept region
5Floyd cycleChase (2:1)"cycle", "repeats forever", array as i→nums[i]fast gains 1/step ⇒ must meet
6List positioningChase (gap/2:1)"k-th from end", "middle of list", one passequal speed preserves the head start
7Parallel mergeParallel"two sorted arrays/lists", "merge", "compare"smaller current element is final
8Expand from centerDiverge"longest/count palindromic substring"every palindrome has a center (2n−1)
9Dutch flag3-pointer chase"sort 0s/1s/2s", "partition into three"four-zone invariant; don't advance mid on high-swap
10Subsequence matchParallel"is subsequence", "by deleting characters"earliest greedy match is safe
Study order: 1 → 4 (the two foundational topologies) → 3 (most-asked composite) → 5 → 6 (linked-list staples) → 7 → 9 → 2 (needs proofs — do after you're fluent) → 8 → 10.
The interview sentence for any of these (per your communication playbook §1.3): "Both pointers only move forward, so it's O(n) total — and moving ⟨this⟩ pointer is safe because ⟨everything it could still pair with is worse⟩. Let me state the invariant before coding."