In a suffix array over one long sequencing read, how does the LCP array expose the longest repeated segment?
answer
- A repeat is a shared prefix
- Sorting puts similar suffixes side by side
- Only neighbouring ranks need looking at
- The largest entry is the answer's length
- The offset beside it gives the position
basics
~20 sThe LCP array records how many leading characters each adjacent pair of lexicographically sorted suffixes shares. The largest entry is the length of the longest segment occurring at least twice, and the offset beside it says where that segment starts.
solid answer
~50 sA segment that occurs twice is a common prefix of two different suffixes, so its length is an LCP value. Sorting the suffixes puts any two that share a long prefix close together, and for ranks `i < j` the shared prefix length equals the *minimum* adjacent LCP across the range between them — a minimum can never exceed the values it is taken over, so the overall maximum is always realised by some neighbouring pair. That collapses an apparently pairwise search into one linear pass over `n-1` LCP entries: take the maximum, and `sa` at that rank gives the starting offset. Given the suffix array, Kasai's algorithm builds the LCP array in O(n), so the whole pipeline is dominated by suffix-array construction. Watch one detail: the two occurrences behind the winning LCP value may overlap.
code
pseudocode · 11 lines// sa[0..n-1] = suffix start offsets, lexicographically sorted
// lcp[1..n-1] = lcp[i] is the shared prefix length of ranks i-1 and i
best = 0
at = -1
for i in 1..n-1:
if lcp[i] > best:
best = lcp[i]
at = sa[i]
// best == 0 -> no segment occurs twice
// otherwise the answer is text[at .. at + best - 1]
// note: the second occurrence starts at sa[i-1] and may overlap the firstgo deeper
Remember what the LCP array holds: for each pair of neighbours in the sorted suffix order, how many leading characters they have in common. Knowing that the biggest entry is the longest repeat's length is the recall being tested.
Explain the mechanics end to end — repeats are shared prefixes, sorting makes the best-sharing pair adjacent, one pass takes the maximum — and be able to state the linear cost of building the LCP array given the suffix array.
Show that you check the answer against the real requirement: whether the two occurrences may overlap, whether the caller wants the position or just a length, and whether construction cost is acceptable at the input size you actually face.
Weigh whether the exact answer is worth its build cost at all. On a huge input, sampling, a bounded-length repeat search, or an approximate detector may serve the downstream decision at a fraction of the memory and time budget.
## The problem, stated without a puzzle A sequencing pipeline hands you one long read — a single string of tens or hundreds of millions of symbols — and you want the longest segment that appears in it at least twice, plus where it appears. The brute-force framing is horrible: comparing all pairs of starting positions is quadratic in the read length before you even account for comparison cost. The suffix-array-plus-LCP framing turns it into two linear-ish passes. ## Why repeats are prefixes Every occurrence of a segment `S` at position `j` means `S` is a prefix of the suffix that starts at `j`. So "`S` occurs at least twice" is the same statement as "`S` is a common prefix of at least two distinct suffixes". The longest repeated segment is therefore the longest common prefix over all pairs of suffixes. This reframing is the whole idea; everything else is bookkeeping. ## The LCP array With the suffixes sorted (that is the suffix array), define `lcp[i]` = the number of leading characters shared by the suffix at rank `i-1` and the suffix at rank `i`. There are `n-1` meaningful entries, indexed from 1 in the convention used here; `lcp[0]` has no predecessor and is left undefined or zero. The key lemma is that for ranks `i < j`, > LCP(suffix at rank i, suffix at rank j) = min( lcp[i+1], lcp[i+2], ..., lcp[j] ). Intuitively: lexicographic order is agreement-then-divergence, and as you walk down the sorted list the shared prefix with a fixed earlier suffix can only shrink or hold. The consequence is immediate and is what makes the algorithm linear: any pairwise LCP is a minimum over adjacent LCPs, and a minimum is never larger than any element it ranges over. So the maximum pairwise LCP equals the maximum adjacent LCP. Scanning neighbours is not a heuristic — it is exhaustive. ## The scan One pass over `lcp[1..n-1]` tracks the largest value seen and the suffix offset that produced it. The answer's length is that maximum; the answer's text is the first `best` characters of the suffix at that rank. If the maximum is zero, no segment repeats — every position starts a distinct-from-the-first-character suffix. ## Cost, stated precisely - Suffix array: O(n) with a linear construction, O(n log n) with prefix doubling. - LCP array: **O(n)** by Kasai's algorithm, given the suffix array and its inverse. The amortised argument is neat — you compute LCPs in order of text position rather than rank, and the running match length drops by at most one per step, so the total work over all steps is linear. - The scan: O(n). Total: linear or `n log n`, dominated entirely by construction. Compare that with the naive pairwise comparison, which is quadratic in the number of positions and worse once comparison cost is counted. That gap is the entire reason the structure exists. ## Three sharp edges **Overlap.** The two occurrences behind the winning LCP value can overlap. A read containing a long homopolymer run — the same symbol repeated 5,000 times — yields an LCP near 5,000 from two suffixes one position apart. If the biological question demands two *disjoint* copies, you need the extra condition `|i - j| >= L` on the pair of offsets. The standard route is a binary search on the candidate length `L`: for a given `L`, split the sorted order into maximal blocks whose adjacent LCPs are all at least `L`, and check whether any block contains two offsets at least `L` apart. Monotonicity in `L` makes the binary search valid. **Multiplicity.** "Occurs at least `k` times" generalises just as cleanly: instead of the maximum single adjacent LCP, take the maximum over windows of `k` consecutive ranks of the minimum LCP inside the window — a sliding-window minimum, still linear. **Position reporting.** Report the offset, not only the length. An answer that produces a number without being able to point into the read has not solved the problem the pipeline actually has. ## The wrong answers this question aims at The first is "you have to compare all pairs of suffixes" — no, sorting already did the pairwise work implicitly, and the adjacent-minimum lemma is why. The second is "the LCP array must itself be built by re-comparing characters, so it's quadratic" — no, linear-time construction exists and its amortised argument is worth being able to sketch. The third is quietly reporting overlapping occurrences as if the question had not asked for two copies.
- Why is checking adjacent pairs enough instead of all pairs?For two ranks `i < j`, the shared prefix length of their suffixes equals the minimum of the adjacent LCP values across the range between them. A minimum never exceeds any value it ranges over, so no distant pair can beat every neighbouring pair inside that range. The best pair overall is therefore always realised by neighbours, and n-1 comparisons cover every possibility.
- How do you force the two occurrences to be non-overlapping?Add the condition that the two offsets differ by at least the candidate length. Binary-search the length `L`: for each `L`, cut the sorted order into maximal blocks whose adjacent LCPs are all at least `L`, and ask whether any block holds two offsets at least `L` apart. The property is monotone in `L`, so the search is valid and each check is linear.
- What does the LCP array itself cost to build?O(n), given the suffix array and its inverse, using Kasai's algorithm. It walks the text in position order rather than rank order and keeps a running match length that decreases by at most one per step, so the total character comparisons across all steps are linear rather than quadratic.
Sorting the suffixes is like alphabetising a shelf of near-identical labels: the two that agree the longest end up as neighbours, so you only ever compare each label with the one next to it.
saying these in an interview costs you the question
- Compares every pair of suffixes and calls the quadratic cost unavoidable
- Claims a non-adjacent sorted pair can beat the best adjacent LCP
- Assumes the LCP array costs more than linear time to build
- Reports only the repeat's length, never where it occurs
- Overlooks that the two occurrences found may overlap