3.8.7String Algorithms

Suffix array — construction O(n log n), LCP array

2,286 words10 min readdifficulty · medium3 backlinks

1. Definitions

Figure — Suffix array — construction O(n log n), LCP array

2. Construction in O(nlogn)O(n\log n) — derived from scratch

HOW the cost comes out to O(nlogn)O(n\log n):

  • Number of rounds =log2n= \lceil\log_2 n\rceil (because kk doubles: 1,2,4,,n1,2,4,\dots,\ge n).
  • Each round sorts nn pairs. With radix sort (two passes on small integer keys [0,n]\in[0,n]) each round is O(n)O(n). → total O(nlogn)O(n\log n).
  • If you use comparison sort each round (O(nlogn)O(n\log n)), total is O(nlog2n)O(n\log^2 n) — still fine and far simpler to code.

3. The LCP array — Kasai's O(n)O(n) algorithm, derived


4. Common mistakes (Steel-man → fix)


Recall Feynman: explain to a 12-year-old

Imagine you cut a word like "banana" into all its tails: "banana", "anana", "nana", "ana", "na", "a". Now put these tails in dictionary order like words in a dictionary. The suffix array is just the page numbers (starting positions) in that sorted list. To sort them fast we're lazy: first we only compare the first letter, then first two letters, then four, doubling each time — because to know the first 4 letters of a tail we glue together two "first 2 letters" answers we already computed. The LCP numbers tell you how many starting letters two neighboring tails share — handy for spotting repeats.


Connections

  • Suffix Tree — same problems, more memory; SA is its flattened cousin.
  • Radix Sort / Counting Sort — the per-round linear sort enabling O(nlogn)O(n\log n).
  • Binary Search on Suffix Array — pattern matching in O(mlogn)O(m\log n).
  • Sparse Table / RMQ — combine with LCP for O(1)O(1) LCP of any two suffixes.
  • Z-Algorithm / KMP — alternative substring tools (single pattern).
  • Burrows–Wheeler Transform — built directly from the suffix array.

Flashcards

What is the suffix array of a string?
The array of starting indices of all suffixes, sorted lexicographically.
What is the rank array and how does it relate to SA?
rank is the inverse permutation: rank[SA[r]] = r; it gives each suffix's sorted position.
Core idea of O(nlogn)O(n\log n) suffix array construction?
Prefix-doubling: sort by first k chars using rank pairs (rank_k[i], rank_k[i+k]), doubling k each round.
Why is the comparison key for the next round a pair of previous ranks?
First 2k chars of suffix i = (first k of suf i)+(first k of suf i+k), whose orders are already known ranks.
How many doubling rounds and total complexity (with radix sort)?
⌈log₂ n⌉ rounds × O(n) each = O(n log n).
What sentinel for rank_k[i+k] when i+k ≥ n, and why?
−1 (smaller than any real rank), because a shorter suffix must sort before a longer one sharing its prefix.
What does LCP[r] measure?
Length of longest common prefix of the two adjacent sorted suffixes suf(SA[r−1]) and suf(SA[r]).
Key lemma behind Kasai's O(n) LCP algorithm?
When moving from suffix i to i+1, the LCP with its predecessor drops by at most 1.
Why does Kasai carry h over instead of resetting to 0?
To keep amortized O(n); resetting causes O(n²) re-scanning.
Formula for number of distinct substrings via SA and LCP?
Σ(n − SA[r]) − Σ LCP[r].
Common mistake about which suffixes LCP compares?
It compares neighbors in sorted (SA) order, not suffixes at index r and r+1.

Concept Map

all suffixes sorted

inverse permutation

inverse of

LCP of adjacent suffixes

same problems, more memory

constructs

uses pair key r_k i, r_k i+k

radix sort each round

log n rounds since k doubles

pattern occurrences form

binary search

enables

String s

Suffix array SA

Rank array

LCP array

Suffix tree

Prefix doubling

Sort by 2k chars

O n log n

Contiguous block

Substring search

Distinct substrings and longest repeat

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Suffix array ka matlab simple hai: ek string ke saare suffixes (tails) ko dictionary order mein sort karke unke starting index store kar lo. Jaise "banana" ke suffixes ko sort karo to milta hai a, ana, anana, banana, na, nana — aur unke indices ka array SA=[5,3,1,0,4,2]SA=[5,3,1,0,4,2] ban jaata hai. Faayda ye ki kisi bhi pattern ko dhoondhna ho to woh sorted suffixes mein ek contiguous block banata hai, jise binary search se pakad lo.

Construction ka asli jugaad hai prefix-doubling. Pure suffix compare karna mehenga (O(n2logn)O(n^2\log n)). Isliye hum lazy hote hain: pehle sirf 1 letter se sort, phir 2 letters, phir 4, doubling karte jao. Trick ye hai ki kisi suffix ke first 2k2k letters ka order = (uske first kk ka rank, aur kk aage wale suffix ke first kk ka rank) — yaani ek pair (rk[i],rk[i+k])(r_k[i], r_k[i+k]). Ye pairs already pata hote hain, to bas pairs ko sort karo. logn\log n rounds, har round O(n)O(n) radix sort se → total O(nlogn)O(n\log n). Ek dhyaan: jab i+ki+k string se bahar jaaye to rank 1-1 (sabse chhota) lo, kyunki chhota suffix pehle aata hai.

LCP array batata hai ki sorted order mein bagal-bagal wale do suffixes kitne shuru ke letters share karte hain. Iska linear-time algorithm Kasai ka hai, jiska core lemma: jab suffix ii se i+1i+1 pe jaate ho, LCP zyada se zyada 1 hi girta hai. Isliye hh ko reset mat karo, bas h1h-1 se shuru karke aage match karo — amortized O(n)O(n). Iska use? Distinct substrings ginna ((nSA[r])LCP[r]\sum(n-SA[r]) - \sum LCP[r]), longest repeated substring (max LCP), waghairah. Yaad rakho: LCP sorted neighbours ka hai, index rr aur r+1r+1 ka nahi — yahi sabse common galti hai.

Go deeper — visual, from zero

Test yourself — String Algorithms

Connections