skip to content

questions

4

In cyclic sort over values 1..n, why does one final scan reveal a missing value?

level: juniorimportance: must knowfreq 48%

answer

  1. The numbers are not labels, they are addresses
  2. One slot per possible value
  3. Where does value v belong
  4. After placement, index i should hold i+1
  5. The first index that fails names the answer

basics

~20 s

Cyclic sort moves every value v to index v-1, so once placement finishes index i must hold i+1. The first index that breaks that rule names the absent value outright, with no extra lookup structure.

solid answer

~40 s

Cyclic sort exploits the fact that when values come from the dense range `1..n`, the value **is** its own address: `v` belongs at index `v-1`. The loop keeps swapping whatever sits in front of it to its home index, advancing only once the current slot is settled. When the pass ends, every value present sits at its own index, so a single scan checking `a[i] == i+1` finds the first mismatch — and that index `i` tells you `i+1` never arrived. In a boarding audit of `n` seat tokens, the slot that fails names the seat nobody scanned, and the value squatting there is the token scanned twice. Cost is O(n) time and O(1) extra space, licensed only because the range is dense and reordering is allowed.

go deeper

for a junior

Recall the core equation: value v lives at index v-1, so after placement index i should hold i+1. Be able to say why the first index that fails that check names the missing value.

for a middle

Explain the swap-until-settled mechanics — when the walk advances, when it does not — and state the three conditions the input must satisfy for the value-as-address trick to be legal at all.

for a senior

Show you check the license before reaching for the pattern: bounded dense range, one slot per value, and written permission to reorder the caller's buffer. Name what you would use instead when one is missing.

for a principal

Frame it as buying O(1) extra space by spending the caller's data ordering. Be ready to say when that trade is worth it on a hot path and when a linear-space alternative is the cheaper answer for the team.

## The setup Imagine a gate agent auditing a boarding pile. There are `n` seat tokens, each stamped with a number from `1` to `n`, and they were scanned into a buffer in whatever order passengers walked up. Exactly one seat is unaccounted for, and one token got scanned twice in its place. You have to name the missing seat. The obvious answers are: sort the pile and look for the gap, or build a membership structure and probe `1..n` against it. Both work. Cyclic sort is the answer that uses a property those two throw away — the numbers on the tokens are not arbitrary labels, they are *addresses*. ## Value as address With values drawn from the dense range `1..n` and exactly `n` slots to hold them, there is one slot per possible value. That is the whole license. It lets you declare a **home**: value `v` belongs at index `v-1`. Nothing has to be compared to anything; each value already knows where it goes. The placement loop is short. Look at the current slot. Compute the home of the value sitting there. If that home already holds its own correct value, this slot is as settled as it will get, so move on. Otherwise swap the current value into its home — which evicts whatever was squatting there into the current slot, where you immediately repeat the process on the newcomer. You do not advance while you are still holding a value that has somewhere to be. When the walk reaches the end, the invariant is: **every value present in the buffer sits at its own home index.** Slots whose value never showed up hold something else — necessarily a duplicate of a value that *did* show up, since the buffer is still full and nothing was created or destroyed by swapping. ## Why one scan suffices Because the post-placement invariant is a per-index equation, checking it is a linear walk with no state: ``` for i in 0..n-1: if a[i] != i + 1: report i + 1 as absent, a[i] as the doubled value ``` The first index that fails the equation names the absent value directly. You never search, never probe a side structure, never compare two tokens to each other. The check is the answer. This is what people mean when they call the pattern "placement, then read-off". The clever half is the placement; the reporting half is deliberately dumb. ## What it costs and what it demands Time is O(n): the placement walk does a bounded amount of work in total (each swap permanently settles one value at its home, so swaps cannot exceed the number of slots), and the read-off is one more pass. Extra space is O(1) — the reordering happens inside the buffer you were handed. That O(1) is not free. It is paid for by three demands on the input: | Demand | Why it is required | | --- | --- | | Values in a dense bounded range | Otherwise `v-1` is not a valid slot number; there is no home to swap into | | One slot per candidate value | Otherwise the equation `a[i] == i+1` is not what a complete input looks like | | Permission to reorder in place | The pattern destroys the arrival order; if the caller still needs it, you cannot use this | Drop any one of them and the pattern is inapplicable, not merely slower. ## The off-by-one that bites The home formula depends on where the range starts. For values `1..n` the home of `v` is `v-1` and the completed state is `a[i] == i+1`. For values `0..n-1` the home of `v` is `v` and the completed state is `a[i] == i`. Mixing the two produces either an out-of-range read on the largest value or a report that is off by exactly one — and the second failure is worse, because it returns a confident wrong answer instead of crashing. Fix the convention before writing the loop and state it out loud in an interview. ## The mental model to carry away Sorting arranges values *relative to each other*. Cyclic sort arranges values *relative to their own numeric identity*. That is why it escapes the comparison framing entirely: it never asks "is this token bigger than that one", only "is this token home yet". When a problem hands you a dense bounded range and permission to mutate, that question is cheaper — and the read-off afterwards is free.

  • After placement, what is sitting at the index whose value is missing?
    A duplicate. Swapping only rearranges, so the buffer still holds `n` tokens; if one value never arrived, some other value arrived twice, and its second copy is what occupies the orphaned slot. That is why the same single scan reports both the absent value and the doubled one in one pass.
  • Does the arrival order of the tokens change the cost?
    No. The bound is structural, not distributional: every swap parks one value at its home permanently, so the total number of swaps is capped by the number of slots no matter how scrambled the input is. An adversary can choose the order and still cannot force more than linear work.
  • If the tokens were numbered 0..n-1 instead, what changes?
    Only the home formula and the final check. Value `v` then belongs at index `v` rather than `v-1`, and the completed state is `a[i] == i`. Keeping the `-1` from the one-based version either reads past the end on the largest value or reports an answer off by one.

Numbered coat hooks: every coat has its own hook number printed on the tag, so instead of sorting the coats you just hang each on its hook and look for the empty one.

saying these in an interview costs you the question

  • Claims the input must already be nearly sorted
  • Thinks it works for arbitrary unbounded values
  • Says it needs an extra lookup structure to report the answer
  • Calls it a comparison sort that happens to be faster
  • Confuses the one-based and zero-based home formula

context

open as a page

Cyclic sort can swap repeatedly without advancing its index — why is the total work still O(n)?

level: middleimportance: must knowfreq 52%

basics

~20 s

Every swap parks one value permanently on its home index, and a settled index is never disturbed again. So total swaps are capped by the slot count, and the cursor advances at most that many times — linear overall.

open as a page

In cyclic sort over values 1..n, why must the swap guard compare values rather than indices?

level: middleimportance: should knowfreq 42%

basics

~20 s

An index-based guard spins forever on duplicates: two slots holding the same value swap identical contents while the cursor never advances. Comparing the current value against what already sits at its home fires the skip branch and guarantees progress.

open as a page

When would you reject cyclic sort for a missing-token audit and reach for something else?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Reject it when the values are not a dense bounded range, or when the buffer is read-only, shared, or must keep its arrival order. Recognising the missing license is the actual skill the question tests.

open as a page