A sorted row of integers, an anchor, and two pointers pinching toward each
other, playing 3Sum (LC 15): every distinct triplet that sums to zero. Under
the row sits the piece’s argument — the triangle of candidate pairs
(lo, hi) for the current anchor, one cell per pair. When the probe’s sum is
too small, lo moves right and its entire row goes dark: every pair with this
lo and a hi at or left of the current one sums to at most what was just
seen. Too big, and hi’s whole column dies for the mirror reason. One
comparison, hi − lo pairs gone. The pointers cross after at most n − i − 2
probes, however many pairs the triangle held, which is the O(n) inner scan
that turns the whole problem quadratic after an O(n log n) sort.
The rest of the piece is the part of the problem that is actually the interview: the duplicates. A dedup switch runs the same input three ways. Correct skips an anchor that equals the value before it (the previous anchor already found everything starting with that value) and, after a hit, steps each inner pointer past its own repeats — skips show as struck-through tiles and cost no probe. None skips nothing, and the same triplet comes off the belt twice, flagged red in the list. Wrong skips an anchor that equals the value after it, the classic off-by-one direction, and the run ends with a triplet it never found, named in red. All three carve the triangle identically. Only the answers differ.
Two smaller things are drawn rather than asserted: once the anchor turns positive the scan stops, with everything to its right greyed out because nothing there can pull a sum back to zero; and the running probe count sits next to the number of triples brute force would examine, so the quadratic against the cubic is a pair of numbers on screen instead of a claim.
Reuse
src/pincer.js is a framework-free ES module:
threeSum(nums)— the plain answer, as you would write it in an interview.makeNums(n, { spread, rand })— n integers in a small range, so duplicates and hits are both common enough to watch.PRESETS—example,random,dupes,zeros,positive, each a function ofn.cleanNums(input, max)— user text into clamped integers.simulate(nums, { dedup })— one frame per step (anchor,probe,skip-anchor,skip-lo,skip-hi,exit,done) with the pointer positions, the sum, which pointer moves, the pairs killed and still alive for that anchor, running probe totals, and the triplets so far.dedupis'correct','none', or'wrong'; the result also carries the duplicates that leaked and the triplets that were missed against the correct set. Same trace-per-tick shape aswindow,sift,skylineandrho.
No rendering or timers in the module; the canvas demo is reference code.
Gotchas
- The alive region of an anchor’s triangle is exactly
lo ≤ a < b ≤ hi, so the frames carry no mask — a renderer derives the carving from the two pointers. Change the pointer rule and the triangle stops being honest. - A hit kills a row and a column,
2·(hi − lo) − 1pairs, because both pointers move. The counter reflects that; a single-pointer move after a hit would be a different (and slower) algorithm. - Inner-pointer skips are counted as pairs ruled out but not as probes, and
the per-anchor bound
n − i − 2counts probes only. That is the honest accounting: a skip is a comparison against a neighbor, not against the target. wrongmode keeps the inner skips correct so the only thing broken is the anchor direction — which is the bug the brief singled out.- The demo bundles its own copy of
pincer.js(self-contained by contract); re-copy after editingsrc/.