Two Pointers
Eleven drills built on one idea: two indices that each move in only one direction can cover an array in O(n) time and O(1) extra space. Opposite ends for sorted pairs and symmetric scans, read/write for in-place compaction, merge from the back, and reversal tricks for rotations and permutations β each with a traced diagram, a compilable Java solution and the bugs to avoid.
Two Indices, One Pass
A brute-force pair search tries every (i, j): O(nΒ²). The two-pointer technique keeps two indices and, at each step, moves exactly one of them based on a local comparison. Because neither pointer ever moves backwards, the total number of moves is bounded by the array length β the whole scan is O(n), and since the state is just two integers, extra space is O(1).
What makes it correct is always a monotonic argument: in a sorted array, moving the left pointer can only increase a sum; in Container With Most Water, the shorter wall can never be part of a better answer; in a compaction, everything left of write is final. When you can state that argument in one sentence, you have a two-pointer solution.
- Hashing buys O(n) time with O(n) memory and works on unsorted data.
- Two pointers buys O(n) time with O(1) memory but needs structure: sorted order, a symmetric question, or an in-place rewrite.
- If the input is sorted (or you are allowed to sort it), try two pointers first β Two Sum II (Drill 18) versus Two Sum (Drill 01) is the classic comparison.
Why Moving One Pointer Is Safe
Ask these questions in order; the first "yes" picks the template.
Four Reusable Templates
Every drill on this page is one of these four loops with a different comparison plugged in. Memorise the loop shapes and the invariants written in the comments β those invariants are what you say out loud in an interview.
a[0..write) is the finished output.Reverse String
Reverse a character array s in place using O(1) extra memory.
Brute force β optimal. Building a reversed copy (or new StringBuilder(str).reverse()) is O(n) time but O(n) space, which the problem forbids. Template D swaps in place: O(n) time, O(1) space. The middle element of an odd-length array never moves β the loop condition left < right skips it automatically.
| Approach | Time | Space | Notes |
|---|---|---|---|
| Copy into a new array | O(n) | O(n) | violates the in-place requirement |
| Two-pointer swap β optimal | O(n) | O(1) | n/2 swaps |
- Loop condition
left <= rightis harmless here (it swaps the middle with itself) buti < ninstead ofi < n / 2in an index-based loop reverses twice β giving back the original. - Missing
import java.util.ArraysforArrays.toStringinmainβ the original drill did not compile. - Java
Stringis immutable; for aStringinput convert withtoCharArray()and back withnew String(chars).
Move Zeroes
Move all zeros in nums to the end while keeping the relative order of the non-zero elements. Modify the array in place.
write. The zeros get pushed rightwards as a side effect, in a single pass.
Brute force β optimal. Bubbling each zero to the end is O(nΒ²). Copying non-zeros into a new array is O(n) but uses O(n) space. The read/write swap is O(n), O(1) and stable for the non-zero values. (Copy-then-fill is equally valid; swapping does fewer writes when there are few zeros.)
| Approach | Time | Space | Notes |
|---|---|---|---|
| Bubble zeros one by one | O(nΒ²) | O(1) | |
| Copy to new array | O(n) | O(n) | |
| Read/write swap β optimal | O(n) | O(1) | single pass, stable |
- Sorting β it reorders the non-zero values, breaking the "relative order" requirement.
- Using opposite-ends swapping (zero at left, non-zero at right) β fast, but also destroys the order.
- Copy-then-fill variant: forgetting the second loop that writes zeros into
[write, n)leaves stale values.
Remove Duplicates from Sorted Array
Given a sorted array, remove duplicates in place so each value appears once, keep the order, and return k β the number of unique values. The first k slots must hold the result.
nums[k β 1], the tail of the output.
Brute force β optimal. A LinkedHashSet rebuild is O(n) time but O(n) space; shifting elements left on every duplicate is O(nΒ²). Template B with "keep if nums[i] != nums[k β 1]" is O(n), O(1). Comparing against the last kept value (rather than nums[i β 1]) generalises directly to LC 80 "allow at most two" by comparing with nums[k β 2].
| i | nums[i] | last kept nums[kβ1] | action | k after |
|---|---|---|---|---|
| 1 | 0 | 0 | dup β skip | 1 |
| 2 | 1 | 0 | nums[1] = 1 | 2 |
| 3, 4 | 1, 1 | 1 | dup β skip | 2 |
| 5 | 2 | 1 | nums[2] = 2 | 3 |
| 6 | 2 | 2 | dup β skip | 3 |
| 7 | 3 | 2 | nums[3] = 3 | 4 |
| 8 | 3 | 3 | dup β skip | 4 |
| 9 | 4 | 3 | nums[4] = 4 | 5 |
| Approach | Time | Space | Notes |
|---|---|---|---|
| Shift left on each duplicate | O(nΒ²) | O(1) | |
| LinkedHashSet rebuild | O(n) | O(n) | |
| Read/write β optimal | O(n) | O(1) |
- Forgetting the
nums.length == 0guard βk = 1would claim one element in an empty array. - Returning the array instead of
k, or printing the whole array (the tail is garbage by design). - Applying this to an unsorted array β duplicates are no longer adjacent; you need a hash set.
Merge Two Sorted Arrays
nums1 has length m + n: its first m values are sorted and the last n slots are empty. Merge the sorted array nums2 (length n) into nums1 so the whole thing is sorted, in place.
nums1 values you have not read yet. The back of nums1 is empty, and the largest remaining element is always one of the two tails β so fill from the back.
Brute force β optimal. Copy nums2 into the tail and call Arrays.sort: O((m+n) log(m+n)). A forward merge into a temporary array is O(m+n) time but O(m) space. The backward merge is O(m+n) time and O(1) space. Loop on j >= 0 only: once nums2 is exhausted, whatever remains of nums1 is already sorted and in place.
| Approach | Time | Space | Notes |
|---|---|---|---|
| Copy + sort | O((m+n) log(m+n)) | O(log(m+n)) | sort stack |
| Forward merge with a copy of nums1 | O(m + n) | O(m) | |
| Backward merge β optimal | O(m + n) | O(1) |
- Merging front-to-back in place β overwrites
nums1[i]before it is read. - Looping on
i >= 0 && j >= 0and forgetting to drain the rest ofnums2(e.g.nums1 = [4,5,6,0,0,0],nums2 = [1,2,3]). The original drill handled it with a second loop; the singlewhile (j >= 0)loop with ani >= 0guard covers both. m = 0:istarts at β1 and every element comes fromnums2β the guardi >= 0prevents an out-of-bounds read.
Rotate Array
Rotate nums to the right by k steps, in place. k may be larger than the array length.
[A | B] β [B | A] where B is the last k elements. Reversing the whole array gives [BΚ³ | AΚ³]; reversing each block again restores their internal order.
Brute force β optimal. Rotating by one, k times, is O(nΒ·k). Copying into result[(i + k) % n] is O(n) time but O(n) space. Three reversals (Template D) are O(n) time β each element is swapped at most twice β and O(1) space. Always reduce k %= n first.
| Approach | Time | Space | Notes |
|---|---|---|---|
| Rotate by one, k times | O(nΒ·k) | O(1) | |
| Extra array, index (i + k) % n | O(n) | O(n) | |
| Three reversals β optimal | O(n) | O(1) |
- Forgetting
k %= n: withk = 10,n = 7,reverse(0, k β 1)indexes past the end. - Empty array:
k % 0throwsArithmeticExceptionβ the original drill had no guard; return early whenn == 0. - Reversing the blocks in the wrong order for a left rotation: left by k = right by n β k.
Two Sum II (Sorted Input)
Given a 1-indexed array numbers sorted in non-decreasing order, find two numbers that add up to target and return their indices [index1, index2] (1-based, index1 < index2). Use only O(1) extra space.
numbers[L] + numbers[R] is too small, then numbers[L] plus anything at or left of R is too small too (R is the biggest still available) β so L is useless and can go. Symmetrically, too big means R is useless.
Brute force β optimal. All pairs: O(nΒ²). For each element, binary-search its complement: O(n log n), O(1). The Drill 01 hash map is O(n) but O(n) space, which this problem forbids. Opposite-ends convergence is O(n) and O(1) and uses the sortedness that the hash map ignores.
| Approach | Time | Space | Notes |
|---|---|---|---|
| All pairs | O(nΒ²) | O(1) | |
| Binary search per element | O(n log n) | O(1) | |
| Hash map (Drill 01) | O(n) | O(n) | ignores sortedness |
| Opposite ends β optimal | O(n) | O(1) |
- Returning 0-based indices β this problem is 1-indexed.
while (left <= right)lets an element pair with itself (e.g. target 8 with a single 4).- Overflow when values are near
Integer.MAX_VALUE: compute the sum aslongor comparenumbers[L] < target β numbers[R].
Reverse Vowels of a String
Reverse only the vowels (a e i o u, upper or lower case) of a string; every other character stays where it is.
Brute force β optimal. Collect vowels into a list, then write them back in reverse: O(n) time, O(n) space, two passes. Two pointers that skip non-vowels do it in one pass with O(1) extra space (beyond the char array a Java String forces you to make). Each pointer moves monotonically, so total work is O(n) even with the inner skip loops.
| Step | L (char) | R (char) | Action | chars after |
|---|---|---|---|---|
| 1 | 0 l β 1 e | 7 e | L skips l; swap e β e | leetcode |
| 2 | 2 e | 6 d β 5 o | R skips d; swap e β o | leotcede |
| 3 | 3 t β 4 c | 4 | L reaches R (L < R fails) β no vowel left | leotcede |
| Approach | Time | Space | Notes |
|---|---|---|---|
| Collect vowels, write back reversed | O(n) | O(n) | two passes |
| Two pointers with skip loops β optimal | O(n) | O(1) extra | plus the unavoidable char[] copy |
- Inner skip loops without
left < rightrun off the end on strings with no vowels (e.g."xyz"). - Forgetting uppercase vowels β
"aA"must become"Aa". - Not advancing both pointers after a swap: the loop swaps the same pair forever.
Container With Most Water
height[i] is a vertical line at x = i. Pick two lines that, with the x-axis, hold the most water; return that area min(height[L], height[R]) Γ (R β L).
Brute force β optimal. Every pair is O(nΒ²). Opposite ends with "move the shorter wall" is O(n): each step discards one line with a proof that it cannot be in a better pair. On ties either side may move β both walls are the bottleneck.
| L, R | area | best | move |
|---|---|---|---|
| 0, 8 | min(1,7) Γ 8 = 8 | 8 | L++ (h[L]=1 < h[R]=7) |
| 1, 8 | min(8,7) Γ 7 = 49 | 49 | R-- (h[L]=8 β₯ h[R]=7) |
| 1, 7 | min(8,3) Γ 6 = 18 | 49 | R-- (h[L]=8 β₯ h[R]=3) |
| 1, 6 | min(8,8) Γ 5 = 40 | 49 | R-- (h[L]=8 β₯ h[R]=8) |
| 1, 5 | min(8,4) Γ 4 = 16 | 49 | R-- (h[L]=8 β₯ h[R]=4) |
| 1, 4 | min(8,5) Γ 3 = 15 | 49 | R-- (h[L]=8 β₯ h[R]=5) |
| 1, 3 | min(8,2) Γ 2 = 4 | 49 | R-- (h[L]=8 β₯ h[R]=2) |
| 1, 2 | min(8,6) Γ 1 = 6 | 49 | R-- (h[L]=8 β₯ h[R]=6) |
| Approach | Time | Space | Notes |
|---|---|---|---|
| All pairs | O(nΒ²) | O(1) | |
| Opposite ends β optimal | O(n) | O(1) | n β 1 steps |
- Moving the taller wall: the area can only shrink, so the optimum (1, 8) above would be skipped.
- Using the sum of heights or the area under the bars β the container is a rectangle limited by the shorter wall.
- Confusing this with Trapping Rain Water (next drill), which sums water over every index.
Trapping Rain Water
Given non-negative bar heights of width 1, compute how many units of rain water are trapped after raining.
min(maxLeft[i], maxRight[i]) β h[i]. You do not need both maxima exactly β only to know which side is lower. If the running leftMax is below the running rightMax, the true right maximum can only be higher still, so the left side decides index L right now.
Brute force β optimal. For each index scan left and right for the maxima: O(nΒ²). Precomputing maxLeft[] and maxRight[] arrays: O(n) time, O(n) space. A monotonic stack also gives O(n)/O(n). Two pointers with two running maxima settle one index per step: O(n) time, O(1) space.
| side | leftMax, rightMax | decision | total |
|---|---|---|---|
| L=0 | 0, 1 | leftMax < rightMax β water[0] = 0 β 0 = 0 | 0 |
| R=11 | 1, 1 | leftMax β₯ rightMax β water[11] = 1 β 1 = 0 | 0 |
| L=1 | 1, 2 | leftMax < rightMax β water[1] = 1 β 1 = 0 | 0 |
| L=2 | 1, 2 | leftMax < rightMax β water[2] = 1 β 0 = 1 | 1 |
| R=10 | 2, 2 | leftMax β₯ rightMax β water[10] = 2 β 2 = 0 | 1 |
| R=9 | 2, 2 | leftMax β₯ rightMax β water[9] = 2 β 1 = 1 | 2 |
| R=8 | 2, 2 | leftMax β₯ rightMax β water[8] = 2 β 2 = 0 | 2 |
| L=3 | 2, 3 | leftMax < rightMax β water[3] = 2 β 2 = 0 | 2 |
| L=4 | 2, 3 | leftMax < rightMax β water[4] = 2 β 1 = 1 | 3 |
| L=5 | 2, 3 | leftMax < rightMax β water[5] = 2 β 0 = 2 | 5 |
| L=6 | 2, 3 | leftMax < rightMax β water[6] = 2 β 1 = 1 | 6 |
| Approach | Time | Space | Notes |
|---|---|---|---|
| Scan both sides per index | O(nΒ²) | O(1) | |
| Prefix max + suffix max arrays | O(n) | O(n) | easiest correct version |
| Monotonic stack | O(n) | O(n) | fills water layer by layer |
| Two pointers β optimal | O(n) | O(1) |
- Updating the max after computing water β
leftMax β h[L]can go negative. Update first, then add (the difference is then β₯ 0). - Comparing
height[L] < height[R]instead of the running maxima is a valid variant only if you are consistent; mixing the two conditions double-counts or misses water. - Fewer than 3 bars can trap nothing; the loop handles it, but test
[],[5],[2, 0].
Next Permutation
Rearrange nums into the lexicographically next greater permutation, in place with O(1) extra memory. If it is already the largest (fully descending), rearrange it into the smallest (ascending).
Brute force β optimal. Generating all permutations and sorting them is O(n!Β·n). The three-step algorithm β find pivot, swap with its successor, reverse the suffix β is O(n) time and O(1) space. Both scans move one pointer leftwards; the reversal is Template D.
| Approach | Time | Space | Notes |
|---|---|---|---|
| Enumerate all permutations | O(n!Β·n) | O(n!) | |
| Pivot + swap + reverse β optimal | O(n) | O(1) | at most 3 linear scans |
- Using
>instead of>=in the pivot scan: with duplicates ([1, 5, 1]) you pick a wrong pivot. The suffix must be non-increasing. - Sorting the suffix instead of reversing it β correct but O(n log n); after the swap the suffix is still non-increasing, so a reversal suffices.
- Forgetting the "no pivot" case (
i == -1): skip the swap and reverse the whole array. - Picking the first element larger than the pivot from the left of the suffix instead of the rightmost one β gives a permutation that is too large.
Partition Array into Two Equal Sum Subarrays
Given an integer array (values may be negative), decide whether it can be cut at one position into a non-empty left part and a non-empty right part β both contiguous β whose sums are equal. Return the cut index (last index of the left part) or β1.
left as a running sum and get right for free as total β left.
Brute force β optimal. For each cut, sum both sides: O(nΒ²). One pass to get the total and one pass with a running left sum: O(n) time, O(1) space. Checking 2 Β· left == total avoids the "is total even?" special case and integer-division bugs. Note this is not LC 416 "Partition Equal Subset Sum" (any subset, not contiguous) β that one is a knapsack DP, covered on the dynamic programming page.
| Approach | Time | Space | Notes |
|---|---|---|---|
| Sum both sides for every cut | O(nΒ²) | O(1) | |
| Total + running left sum β optimal | O(n) | O(1) | long avoids overflow |
- The original drill claimed
[1, 5, 11, 5]returns true via[1, 11] + [5, 5]β those parts are not contiguous. With contiguous parts the answer is false (and the original code actually printedfalse). - Early exit
if (sum > target) return falseis wrong once negatives are allowed:[2, -1, 3, 2, 2]crosses half and comes back. - Letting the cut sit after the last element:
[0]or[3, -3]would "split" into a part and an empty part. Loop ton β 2. intoverflow on large inputs: accumulate inlong.
Summary & What to Practice Next
| Drill | Problem | Pattern | Time | Space | Difficulty |
|---|---|---|---|---|---|
| 13 | Reverse String | opposite ends | O(n) | O(1) | Easy |
| 14 | Move Zeroes | read / write | O(n) | O(1) | Easy |
| 15 | Remove Duplicates from Sorted Array | read / write | O(n) | O(1) | Easy |
| 16 | Merge Two Sorted Arrays | merge from back | O(m + n) | O(1) | Easy |
| 17 | Rotate Array | reverse ranges | O(n) | O(1) | Medium |
| 18 | Two Sum II (sorted) | opposite ends | O(n) | O(1) | Medium |
| 19 | Reverse Vowels of a String | opposite ends + skip | O(n) | O(1)* | Easy |
| 20 | Container With Most Water | move shorter wall | O(n) | O(1) | Medium |
| 21 | Trapping Rain Water | running max pointers | O(n) | O(1) | Hard |
| 22 | Next Permutation | pivot + reverse | O(n) | O(1) | Medium |
| 23 | Partition into Two Equal Sum Subarrays | prefix vs. total | O(n) | O(1) | Easy |
* O(1) beyond the char[] copy that Java's immutable String requires.
- Say the discard argument out loud β "L can never be part of a better answer becauseβ¦". If you cannot, two pointers is probably the wrong tool (Drills 18, 20, 21).
- Read/write invariant:
a[0..write)is final. Only the keep-condition changes between problems (Drills 14, 15). - In-place merge? Go backwards β the empty space is at the back (Drill 16).
- Reversal is a building block: rotate = three reversals; next permutation = pivot, swap, reverse (Drills 13, 17, 22).
- Guard every inner loop with
L < Rand every% nwithn > 0(Drills 17, 19).
- Continue to Coding Drills 03 Β· Strings & Sliding Window β the same-direction pointers grow into a window.
- Back to Coding Drills 01 Β· Arrays & Hashing to compare Two Sum (hash map) with Two Sum II (pointers).
- Theory and more patterns (fast/slow, 3Sum, Dutch flag): Two Pointers technique, Sliding Window, Sorting.
- More problems: LeetCode Β· Two Pointers and Array fundamentals.