Lesson 2 of 8 · 48 min
Two pointers on arrays and lists
Opposite-end, same-direction, and fast/slow pointer families — invariants, complexity, production metaphors (stream merge, retry loops), and 3Sum discipline.
When sorted input unlocks linear time
Key idea
Opposite ends: pair sums, containers, 3Sum skeleton
1OPPOSITE ENDS — Container With Most Water (narrate the move rule)23def max_area(height: list[int]) -> int:4 l, r = 0, len(height) - 15 best = 06 while l < r:7 best = max(best, min(height[l], height[r]) * (r - l))8 # Move the shorter side — width shrinks, only taller min can improve.9 if height[l] < height[r]:10 l += 111 else:12 r -= 113 return best1415# Dry-run [1,8,6,2,5,4,8,3,7]: start ends 1 and 7; move left (shorter) toward 8…16# Complexity: O(n) time, O(1) space — say it before coding.Same-direction: compact, partition, remove
nums[0:write] is the answer so far; read ≥ write. This is how you rewrite filters without allocating — interviewers listen for that production parallel.1SAME-DIRECTION — remove duplicates from sorted array (in-place)23def remove_duplicates(nums: list[int]) -> int:4 if not nums:5 return 06 w = 1 # next write position7 for r in range(1, len(nums)):8 if nums[r] != nums[w - 1]:9 nums[w] = nums[r]10 w += 111 return w # new length; nums[:w] unique sorted1213# Invariant: nums[:w] is compacted unique prefix.14# O(n) time, O(1) extra space.Common mistake
“Two pointers only apply when the array is sorted.”
Fast & slow: cycle, middle, kth-from-end
1FAST/SLOW — has cycle (Floyd). O(1) space vs hash set of nodes.23class ListNode:4 def __init__(self, val=0, next=None):5 self.val, self.next = val, next67def has_cycle(head: ListNode | None) -> bool:8 slow = fast = head9 while fast and fast.next:10 slow = slow.next11 fast = fast.next.next12 if slow is fast:13 return True14 return False1516# Dry-run: acyclic 1→2→3→None: fast hits None. Cyclic 1→2→3→2: eventually meet.17# Why it works: relative speed 1 closes the gap in a loop.Key idea
3Sum: where two pointers meet sorting discipline
Common mistake
“If two pointers fail a dry-run, switch to DP immediately.”
Complexity and failure modes you must narrate
while l < r vs l <= r is the most common live bug: dry-run a two-element array every time.Key idea
Container With Most Water — NeetCodeNeetCodeInterview prep
- 01“When do you use two pointers?” → sorted pair/partition problems; same-direction compact; fast/slow on lists for cycle/mid/kth.
- 02“Why move the shorter line in Container With Most Water?” → width always shrinks; only a taller min(height) can improve area.
- 03“Cycle detection without O(n) memory?” → Floyd fast/slow; relative speed guarantees meet if cycle exists.
- 04“Remove Nth from end in one pass?” → lead pointer by n (or n+1 with dummy head), then advance both.
- 05“3Sum duplicates?” → sort + skip equal outer and equal inner pointers after a hit.
- 06“Sorted vs hash for Two Sum?” → need indices on unsorted → hash; sorted Two Sum II → two pointers O(1) space.
- 07“Production story?” → merge sorted streams; detect retry loop; compact filtered buffer in place.
- 08“Complexity you must say?” → usually O(n)/O(1); 3Sum O(n²)/O(1) or O(log n) if sort space counts.
- 09"Trapping rain without O(n) arrays?" -> two pointers + maxLeft/maxRight; resolve smaller side.
- 10"Dutch flag invariant?" -> [0,low) zeros, [low,mid) ones, (high,n] twos.
- 11"Merge sorted arrays from end?" -> write pointer must not overwrite unread values.
- 12"Cycle II one-liner?" -> head-to-entrance equals meeting-to-entrance along cycle; reset equalizes.
articleTwo Pointers pattern — AlgoMasterAlgoMasterdocsNeetCode practice roadmapNeetCodearticleFloyd cycle detection — WikipediaWikipediaarticleTech Interview Handbook — coding patternsTech Interview HandbookState the invariant before the while-loop. If you cannot say what is true of L and R at every iteration, you are guessing moves — and dry-runs will fail.
Senior aloud answers + rain water / 3Sum code
1TRAPPING RAIN WATER — two pointers O(1) space23def trap(height: list[int]) -> int:4 if not height:5 return 06 l, r = 0, len(height) - 17 left_max = right_max = water = 08 while l < r:9 if height[l] < height[r]:10 if height[l] >= left_max:11 left_max = height[l]12 else:13 water += left_max - height[l]14 l += 115 else:16 if height[r] >= right_max:17 right_max = height[r]18 else:19 water += right_max - height[r]20 r -= 121 return water2223# Smaller side is safe to resolve: opposite side is a taller wall.24# Alt: prefix/suffix max arrays O(n) space — state both.13SUM — sort + opposite ends + duplicate skip23def three_sum(nums: list[int]) -> list[list[int]]:4 nums = sorted(nums)5 n, out = len(nums), []6 for i in range(n):7 if i and nums[i] == nums[i - 1]:8 continue9 l, r = i + 1, n - 110 while l < r:11 s = nums[i] + nums[l] + nums[r]12 if s < 0:13 l += 114 elif s > 0:15 r -= 116 else:17 out.append([nums[i], nums[l], nums[r]])18 l += 1; r -= 119 while l < r and nums[l] == nums[l - 1]:20 l += 121 while l < r and nums[r] == nums[r + 1]:22 r -= 123 return out # O(n^2) after O(n log n) sortCommon mistake
"Sorting an unsorted array just to use two pointers is always smart."
Common mistake
"Floyd meeting point is the cycle entrance."
Key idea
Checkpoint
Interviewer: sorted array of ints, find if any pair sums to T. You start writing a hash set. What is the stronger opening?
Checkpoint
Dry-run cycle detection: list 1→2→3→4→2 (cycle at 2). Fast and slow start at 1. After enough steps they meet. Where?
Checkpoint
Container With Most Water: height[l] == height[r] and l < r. Which move is safe?
Checkpoint
You need the middle of a singly linked list of unknown length. Best approach in an interview?
Checkpoint
3Sum returns duplicate triplets. Root cause in a typical two-pointer solution?
Can you pick opposite-end vs same-direction vs fast/slow on a fresh prompt and dry-run the invariant?
Takeaways
- Three families: opposite ends (sorted pairs/areas), same-direction (compact/partition), fast/slow (lists).
- Always state the invariant and complexity before the while-loop.
- Sorted input is a gift — O(1) space two pointers often beats hash.
- 3Sum difficulty is duplicate control, not the pointer mechanic.
- Production bridges: merge streams, retry loops, in-place filter buffers.
Next: sliding window — fixed and variable windows, and the “at most K” template.
Sources
Free to read · better with Enzo
Learn it with Enzo
Save your progress, answer the checkpoints, and let Enzo quiz you on what you just read.