Both pointers move in the same direction. The read pointer visits every element; the write pointer only moves when you place something you want to keep. It's the standard way to filter, deduplicate or compress an array in place with extra space. Cyclic sort is a close cousin: when values belong at known indices, swap each one home.
The idea#
Everything to the left of the write pointer is the finished answer. Everything between write and read has already been looked at and is free to overwrite. That invariant (a rule that stays true at every step) is why the trick is safe: you never overwrite a value you still need to read. Three rules keep it true:
- The read pointer always moves forward, one step per iteration.
- The write pointer moves only when you place a valid element.
- At the end, the write pointer equals the length of the kept prefix.
Read/write pointers keep values in their original order. Cyclic sort solves a different in-place problem: it uses the values themselves as addresses. If the array should hold 1..n, then value v belongs at index v - 1. For each position there are three cases:
- The number is already in its right position: move on.
- The number is in the wrong position: swap it to its right position, and check the same index again (a new value just arrived there).
- The number is out of range (
<= 0or> n): it has no home, so leave it and move on.
Each swap puts at least one value home for good, so there are at most swaps and the whole pass is . Afterward, walk from the start; the first index whose value doesn't match is the answer.
How to spot it#
Both techniques answer "do it in place" questions, so the cues overlap. Look for these:
- "Do it in place" or " extra space" with a filter, dedupe or compress step.
- "Return the new length
k, and the firstkslots must hold the answer." - Values lie in
1..n(or0..n) and the question is about missing or duplicate numbers. - Moving certain values (zeros, a target) to one end while keeping the order of the rest.
Template#
The read/write loop has one decision: keep the value or skip it.
write = 0 # items[:write] is the finished answer
for read from 0 to n - 1:
if keep(items[read]): # compare with items[write - 1] to dedupe
items[write] = items[read] # copy from read, never from write
write += 1
return write # length of the kept prefix
Cyclic sort#
Cyclic sort turns the three cases from above into code. It uses a while loop, because the index advances only when nothing moves.
i = 0
while i < n:
v = items[i]
if v has a home and items[home(v)] != v:
swap items[i] and items[home(v)] # send v home, recheck i
else:
i += 1 # already home, a duplicate, or no home
for each index i:
if items[i] is not the value that belongs there:
report i
The items[home(v)] != v check covers both "already home" and "a duplicate is already sitting there". Without it, two equal values would swap forever.
Pitfalls#
These are the mistakes that most often cost a correct answer.
- Read from the read pointer. Once the two pointers diverge, the value at
writemay be stale. Always copy fromread. - Off-by-one on the returned length. Decide up front whether
writeis "next slot to fill" (returnwrite) or "last filled slot" (returnwrite + 1) and stick to it. - Cyclic sort must not advance after a swap. The value that arrived at index
ihasn't been placed yet. - Cyclic sort needs a duplicate guard. Swap only when the destination doesn't already hold
v.
Problems in this pattern#
These problems use this pattern. Solve them before you open the hints below. Start with the first three.
- 27. Remove Element
- 26. Remove Duplicates from Sorted Array
- 283. Move Zeroes
- 443. String Compression
- 287. Find the Duplicate Number
- 41. First Missing Positive
Hints#
Open a hint only after you have tried the problem for a while.
Hint: Remove Element
Keep a write pointer at the slot to fill next. Whenever the read pointer finds a value other than val, copy it to the write slot and advance write.
Hint: Remove Duplicates from Sorted Array
The input is sorted, so equal values sit together. Start write at 1 and keep nums[read] when it differs from nums[write - 1], the last value you kept.
Hint: Move Zeroes
Use the same loop, but swap instead of copy. The zeros drift to the end, and the non-zeros keep their relative order.
Hint: String Compression
The read pointer walks to the end of a run of equal characters. Then the write pointer writes the character and, if the run is longer than 1, the digits of its length. Take the character from the read side (chars[read]), because the write position may already be overwritten.
Hint: First Missing Positive
This is cyclic sort with value v at home index v - 1. Values <= 0 or > n have no home, so leave them. After the pass, the first index i with nums[i] != i + 1 gives the answer i + 1; if none, the answer is n + 1.
Hint: Find the Duplicate Number
Read the values as pointers. With values in 1..n and length n + 1, i -> nums[i] defines a linked list with a cycle, and the duplicate is the cycle's entry. Use Floyd's fast/slow pointers; see Fast/Slow & Gap Pointers.
Key takeaways#
- Read/write keeps the invariant "everything left of
writeis the final answer". - The read pointer always moves; the write pointer moves only when you keep something.
- Cyclic sort sends each value
vto indexv - 1, then the first mismatch is the answer. - After a cyclic-sort swap, stay on the same index; guard against swapping a duplicate onto itself.
- Values in
1..ncan also be read as pointers, which turns a duplicate search into cycle detection.