A sorted array of distinct values, rotated at a pivot, drawn as bars so the
rotation reads as a sawtooth: one rising run, a drop, a second rising run.
Search in Rotated Sorted Array (LC 33) runs across it one probe at a time.
Every probe does two things and the canvas draws both: it compares
nums[lo] with nums[mid] to decide which half is the clean, sorted
one — that half lights green — and it tests whether the target sits inside
that half’s known value range. The target is a horizontal dashed line, so
“inside the range” is literally whether the line crosses the lit bars. Yes:
search there. No: the other half, by elimination, without ever looking at
it. Under the bars, one row per probe shows the window halving.
The point of the piece is the switch labelled lo/mid test. The default
nums[lo] ≤ nums[mid] is correct. Flip it to < and nothing changes until
the window shrinks to two elements — where lo == mid, the comparison is an
element against itself, and it comes back false. The left half is wrongly
declared unsorted, the right half is trusted instead, and if the rotation
point sits between those two elements the target is thrown away with the half
it lives in. The lit half turns red the moment it is declared sorted and
isn’t, and the readout says WRONG with the index the target was actually
at. The [3,1] case button builds the smallest input that triggers it.
The method select swaps in the two-pass answer: a binary search for the
minimum (LC 153, purple rows) followed by a plain binary search in whichever
segment can hold the target (blue). More probes, two loops, two sets of
boundary conditions — and each loop is the textbook one. Set n, drag the
rotation, type a target or click a bar to make its value the target.
Reuse
src/cleave.js is a framework-free ES module:
makeSorted(n, { span, rand })— n distinct non-negative ints, sorted.rotate(sorted, k)— the problem’s rotation,[k..n−1] + [0..k−1].pivotOf(nums)— index of the minimum; 0 when not rotated.searchRotated(nums, target)— the plain one-pass answer, as you would write it in an interview.findMinIndex(nums)/searchTwoPass(nums, target)— LC 153 and the two-pass answer built on it.simulate(nums, target, { method, strict })— one frame per probe with the window, the probe, which half was declared sorted and whether it really is, the comparison as made, the range test, what was eliminated, and a one-sentence note.method: 'two-pass'yieldspivotframes, asegmentframe, then plainsearchframes;strict: trueuses<for the lo/mid test and reproduces the bug. Same trace-per-tick shape aswindow,rhoandsift.
No rendering or timers in the module; the canvas demo is reference code.
Gotchas
- Under
strictthe wrong answer only appears when a two-element window contains the rotation point; on most random inputs the search never lands on one and the bug stays invisible. That is the lesson — it fails no obvious test — and why the preset exists. n = 1and rotation 0 are both legal and both degenerate to a plain binary search; the two-pass method emits nopivotframes onn = 1.- The demo bundles its own copy of
cleave.js(self-contained by contract); re-copy after editingsrc/.