Introduction

Take any string — say banana. A suffix is the tail starting at some position: banana, anana, nana, ana, na, a. Sort those six suffixes alphabetically and you get a neat ranked list. That ranked list, stored as an array of start positions, is the suffix array (SA).

Why bother? With a suffix array you can binary-search for any pattern in O(mlogn)O(m \log n) time (mm = pattern length, nn = text length), locate all occurrences in seconds, and answer dozens of string questions — longest repeated substring, longest common substring of two texts, compression statistics — in linear time using the companion LCP array (longest common prefix between consecutive suffixes).

The catch is construction. Sorting nn suffixes naively costs O(n2logn)O(n^2 \log n) comparisons because each comparison can touch up to nn characters. For a human genome (n3×109n \approx 3 \times 10^9) that is completely off the table. The race to reach O(n)O(n) time took decades, produced three independent solutions around 2003–2009, and remains one of the cleanest stories in algorithm design.

Sorted Suffixes

Type any short word below. The demo builds its suffix array with a clean O(nlog2n)O(n \log^2 n) prefix-doubling sort (fast enough to follow by eye), then highlights the LCP — how many characters each consecutive pair of sorted suffixes shares.

<p class="hint">{{hint}}</p>
<div class="input-row">
  <input id="word" type="text" maxlength="20" value="banana" autocomplete="off" spellcheck="false" />
  <button id="run" type="button">{{btn_build}}</button>
</div>
<div id="output" class="output"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.input-row { display: flex; gap: .5rem; margin-bottom: .9rem; }
#word { font: 700 16px ui-monospace, monospace; padding: .4rem .6rem;
        border: 1px solid #adb1b8; border-radius: 8px; width: 180px; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem;
         border: 1px solid #1d3557; background: #1d3557; color: #fff;
         border-radius: 8px; cursor: pointer; }
.output { overflow-x: auto; }
table { border-collapse: collapse; font-size: .88rem; min-width: 340px; }
th { background: #1d3557; color: #fff; padding: .3rem .7rem; text-align: left; }
td { padding: .28rem .7rem; border-bottom: 1px solid #e2e8f0; }
tr:last-child td { border-bottom: none; }
tr:nth-child(even) td { background: #f4f7fa; }
.sa-idx { font-weight: 700; color: #1d3557; font-family: ui-monospace, monospace; }
.lcp-val { font-family: ui-monospace, monospace; color: #0a7d33; }
.lcp-zero { color: #aaa; }
.suffix-text { font-family: ui-monospace, monospace; }
.suffix-shared { background: #d0f0de; border-radius: 3px; padding: 0 1px; }
// Code not found

Notice how the LCP values cluster near long repeated substrings. The sum of all LCP values equals the total number of shared prefix characters across consecutive sorted suffixes — a quantity that drives compression and the pattern matching algorithms built on suffix arrays.

The Real Complexity

The complexity story has four clean chapters:

  • Naïve sort — O(n2logn)O(n^2 \log n). Sort nn suffixes with a comparison sort; each comparison costs up to O(n)O(n) character reads. Unusable beyond a few thousand characters.
  • Prefix doubling — O(nlog2n)O(n \log^2 n). Manber & Myers (1993) observed that once you rank suffixes by their first 2k2^k characters, you can rank them by their first 2k+12^{k+1} characters in O(nlogn)O(n \log n) work per doubling. Total cost: O(logn)O(\log n) rounds at O(nlogn)O(n \log n) each. An elegant bridge but still super-linear.
  • DC3 / Skew — O(n)O(n). Kärkkäinen & Sanders (2003) proved the first linear bound. Their skew algorithm splits positions into those whose index is divisible by 3 and those that are not, recursively sorts the 2/3-sample, then merges. A graduate-student masterpiece.
  • SA-IS — O(n)O(n). Nong, Zhang & Chan (2009) gave a cleaner linear algorithm based on induced sorting: classify each suffix as S-type or L-type, identify a small set of LMS (leftmost S) suffixes, sort them recursively, then induce the order of all other suffixes in two linear scans. SA-IS uses less memory than DC3 and handles integer alphabets directly.

The lower bound in the comparison model is Ω(nlogn)\Omega(n \log n) (you need to sort), but over integer alphabets of size σn\sigma \leq n the O(n)O(n) algorithms are tight. This is a solved, proven result — not open, not a Millennium Prize — but the journey from naïve to linear is a showcase of algorithmic technique that sits alongside sorting lower bounds and pattern matching in every advanced algorithms course.

Where It Matters

Suffix arrays are the invisible skeleton of several industries:

  • Genome assembly and alignment: tools like BWA and Bowtie index a reference genome with a suffix array (augmented with the Burrows-Wheeler Transform) to align millions of short reads per second. Without linear-time construction the human genome index would take days to build.
  • Full-text search engines: Apache Lucene and similar systems use inverted indices, but suffix arrays handle richer queries (substring search, approximate match) that inverted indices cannot.
  • Data compression: the Burrows-Wheeler Transform (BWT) reorders a text so similar characters cluster, enabling gzip-beating compression (bzip2, LZMA). The BWT is computed almost instantly once you have the suffix array.
  • Plagiarism and duplicate detection: the longest common substring of two documents is found in O(n+m)O(n+m) time by merging their suffix arrays and scanning LCP values.
  • Bioinformatics beyond DNA: protein sequence search (BLAST-like tools) and RNA structure prediction both lean on suffix-array techniques.

The pattern here is the same as with pattern matching: invest O(n)O(n) in preprocessing, then answer any query in near-constant time.

Conclusion

A suffix array is just a sorted list of string tails. But building that list in linear time — without the overhead of a suffix tree — required two decades of algorithmic creativity, from Manber & Myers's prefix doubling to Kärkkäinen & Sanders's skew trick to Nong, Zhang & Chan's induced-sorting tour de force.

The result is proven optimal over integer alphabets: O(n)O(n) time, O(n)O(n) space, no hidden constants worth worrying about. It is not an open problem or a Millennium Prize — it is a closed chapter, celebrated precisely because the path from O(n2logn)O(n^2 \log n) to O(n)O(n) is so instructive.

Every time a genome sequencer aligns a read in milliseconds, or bzip2 shrinks a file by 70 %, or a search engine finds every occurrence of a phrase in a billion-word corpus, a suffix array built in linear time is doing the work. That is what it looks like when an algorithm is not just correct but beautifully, provably efficient.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/suffix-array-construction/Content licensed under CC BY-NC 4.0.