skip to content

In a triple-sum enumeration on sorted input (fix a[i], converge lo/hi on the rest), why does skipping i when a[i] == a[i+1] drop valid triples?

level: middleimportance: should knowfreq 46%

answer

  1. which occurrence does the skip keep?
  2. the fixed element's partners live to its right
  3. processing the last copy leaves no copies as partners
  4. skip against the previous element, after processing
  5. on a match, both pointers pass their duplicates

basics

~20 s

Skipping while the fixed value equals the next one processes only the last copy, whose search window holds no more copies — so triples needing two equal values are lost. Skip against the previous element instead, after the first occurrence is processed.

solid answer

~50 s

The test `a[i] == a[i+1]` skips the first occurrences of each repeated value and processes only its last copy. But the fixed element's partners live strictly to its right (`lo` starts at `i + 1`), so when the last copy of value v is the one processed, no copies of v remain in the window — any triple needing two copies, like (5, 5, 9) for target 19, is never generated. The correct guard is the mirror: skip i when `i > 0 and a[i] == a[i-1]`, which processes the first occurrence with all its duplicates still ahead of it and skips only the redundant re-runs. The same discipline applies on a match inside the window: record it, then advance both pointers past all equal values — advancing just one re-emits the same value pair.

code

pseudocode · 15 lines
pseudocode
sort(a)
for i in 0..n-3
    if a[i] == a[i+1]
        continue        // intended: avoid duplicate triples
    lo = i + 1
    hi = n - 1
    while lo < hi
        s = a[i] + a[lo] + a[hi]
        if s == target
            record(a[i], a[lo], a[hi])
            ...
        else if s < target
            lo = lo + 1
        else
            hi = hi - 1

go deeper

for a junior

Know that duplicate-heavy sorted input is where naive pair and triple enumeration breaks, and that skip-duplicates logic has a right and a wrong placement. Be able to walk a tiny example like [2, 5, 5, 5, 9].

for a middle

Be ready to trace both skip placements over a run of equal values and show exactly which triple disappears, then state the two-sided rule: skip after processing the first occurrence, and advance both pointers past duplicates on a match.

for a senior

An interviewer expects you to catch this bug in code review: probe with a duplicate-heavy test vector, and pin down the output contract — distinct value tuples versus index tuples — before judging any skip logic.

for a principal

Use this as a case study in specification-first review: most duplicate bugs are an undecided output contract, not a coding slip. Push teams to state whether results are value-sets or index-sets before anyone optimizes the enumeration.

## The setting A debate stage is being seated from a roster sorted by influence score, and production wants trios whose scores sum to a neutral target. **Duplicate-heavy input** is the norm — many guests share a score — and duplicates are exactly where naive converging-pointer enumeration goes wrong, in two distinct places. ## The enumeration skeleton 1. Sort the input. 2. For each index `i` (the fixed element), run a converging scan with `lo = i + 1`, `hi = n - 1`, looking for `a[lo] + a[hi] == T - a[i]`. To emit *distinct value triples* rather than repeats, duplicate values must be skipped at both levels — and the placement of those skips is the whole question. ## Bug site one: skipping the fixed element too early The tempting guard `if a[i] == a[i+1] then continue` skips *forward* through a run of equal values and processes only its **last** occurrence. But the fixed element's candidate partners live strictly to its right. Process the last copy of value `v` and no copies of `v` remain in the window — any triple needing two copies, `(v, v, w)`, becomes unreachable. Trace `a = [2, 5, 5, 5, 9]`, target 19. The only qualifying triple is `(5, 5, 9)`. - **With the buggy guard**, `i` skips indices 1 and 2 (each 5 equals the next) and fixes the 5 at index 3; the window is just `{9}`, which contains no pair summing to 14. Output: nothing. - **With the correct guard** — `if i > 0 and a[i] == a[i-1] then continue` — the *first* 5 (index 1) is processed with window `{5, 5, 9}`, and `5 + 9 = 14` yields the triple; the later copies are then skipped as redundant re-runs of the same fixed value. The rule in one line: **skip duplicates after processing the first occurrence, never before it.** The backward-looking test keeps answers that use repeated values while still eliminating repeated work. ## Bug site two: the match step inside the window When `a[lo] + a[hi]` matches and the pair is recorded, advancing only one pointer leaves the other sitting on the same value; the next iteration re-derives the same *value* pair through a different index. For distinct-value output, advance **both** pointers past all copies of their current values after recording. That skip is safe for the same reason the outer one is: it happens after the first occurrence of the pair has been processed. ## The sandwich fact If a match occurs with `a[lo] == a[hi]` and `lo < hi`, sorted order pins every element strictly between them to that same value. The window then holds exactly one distinct value pair but `k(k-1)/2` index pairs across `k` copies. This forces a specification question that should be settled before any skip logic is written: is the output *distinct value tuples* or *index tuples*? - **For value tuples**, record once and close the window. - **For index tuples**, count the `k choose 2` combinations arithmetically rather than stepping pointers one at a time. Many "duplicate bugs" found in review turn out to be this contract left undecided. ## Complexity is unchanged Every skip advances an index that would advance anyway, so the enumeration remains O(n²) after an O(n log n) sort, with O(1) auxiliary space beyond the output itself. Duplicate handling is purely a **correctness concern** — no lost answers, no repeated answers — not a performance lever. ## What interviewers listen for Anyone can say "skip duplicates." The signal is placement and direction: - the backward-comparing skip after processing at the outer level; - the both-pointer skip after recording at the inner level; - and the sandwich observation when equal values meet. And the practical habit: bring a duplicate-heavy vector like `[2, 5, 5, 5, 9]` to any review of this code — it kills the wrong version instantly.

  • During the inner scan you hit a match where a[lo] == a[hi] with lo < hi. What does sortedness tell you about the elements between them?
    They are all equal to that same value — sorted order sandwiches them. The window holds exactly one distinct value pair but k(k-1)/2 index pairs across k copies. So decide the output contract first: distinct value pairs mean record once and close the window; index pairs mean count k choose 2 arithmetically instead of stepping one position at a time.
  • Does duplicate-skipping change the asymptotic complexity of the enumeration?
    No. Each skip advances an index that would advance anyway, so the work stays O(n) per fixed element and O(n^2) overall after the O(n log n) sort, with O(1) auxiliary space beyond output. Skip placement affects only correctness — whether answers are lost or repeated — never the bound.

saying these in an interview costs you the question

  • Skips duplicate fixed values before processing their first occurrence
  • Advances only one pointer after recording a matching pair
  • Assumes emitting duplicates and deduplicating afterwards is free
  • Cannot say what sorted order implies between equal pointer values

context