3.8.7 · D4String Algorithms

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

2,089 words9 min readBack to topic

Level 1 — Recognition

Recall Solution L1.1

Put the four tails in dictionary order. Compare letter by letter:

  • (from index 2) vs (index 0): they agree on ab, then abab continues while ab ends. A string that ends is smaller (shorter prefix wins). So .
  • Both a-starting tails come before both b-starting tails.
  • Among b: (index 3) vs (index 1): b ends first → smaller. So .

Sorted: .

Recall Solution L1.2

is the inverse permutation: . Walk :


Level 2 — Application

Recall Solution L2.1

Glue "first-2-letters" from two "first-1-letter" answers. Off the end :

i=0: (r1[0], r1[2]) = (0, 0)
i=1: (r1[1], r1[3]) = (1, 1)
i=2: (r1[2], -1)    = (0, -1)
i=3: (r1[3], -1)    = (1, -1)

Sort pairs ascending (compare first component, then second): , , , . Matches L1.1 exactly — two characters already fully separated these suffixes, so this round is final. (Uses Radix Sort / Counting Sort on the pair components for per round.)

Recall Solution L2.2

Walk by starting index , predecessor , extend while , then :

i=0 rank=1 pred=SA[0]=2: suf0=abab vs suf2=ab -> match "ab"=2. LCP[1]=2. h=1
i=1 rank=3 pred=SA[2]=3: suf1=bab  vs suf3=b  -> match "b" =1. LCP[3]=1. h=0
i=2 rank=0 : no predecessor -> h=0
i=3 rank=2 pred=SA[1]=0: suf3=b    vs suf0=abab -> match ""=0. LCP[2]=0. h=0

Store by sorted position, by convention: Reading it: neighbors (ab,abab) share 2; (abab,b) share 0; (b,bab) share 1. ✅


Level 3 — Analysis

Recall Solution L3.1
  • Total prefixes over all suffixes: .
  • Overlaps double-counted: .
  • Distinct .

Sanity list: length-1 a,b; length-2 ab,ba; length-3 aba,bab; length-4 abab. That is . ✅

Recall Solution L3.2

Suffix has run out of characters — its true tail is shorter. A shorter string that is a prefix of the other must come first in dictionary order. The rule sets and keeps . Since , the pair sorts before , placing suffix first. ✅ Correct. If we had used (or garbage) for the off-end, the tie would break arbitrarily and could place the longer suffix first — a bug. The sentinel encodes "end-of-string is smaller than any letter".

Recall Solution L3.3

runs . We stop when : . So we need , reached after 10 doublings from (). This is . Each round is with radix sort → total .


Level 4 — Synthesis

Recall Solution L4.1

at . So the longest repeated substring has length , starting at : Check: ana occurs at index 1 (b**ana**na) and index 3 (ban**ana**). ✅ Why LCP finds it: a repeated substring is a common prefix of two different suffixes; the largest such common prefix among all pairs is achieved by some adjacent pair in sorted order (sorting clusters similar tails together), which is precisely what the LCP array records. See figure.

Figure — Suffix array — construction O(n log n), LCP array
Recall Solution L4.2

Because lists tails in dictionary order, every tail beginning with na sits together. The sorted tails are:

r=0 SA=5 a
r=1 SA=3 ana
r=2 SA=1 anana
r=3 SA=0 banana
r=4 SA=4 na
r=5 SA=2 nana

Tails whose prefix is na: rows (na) and (nana). Block , so na occurs 2 times, at positions and , i.e. indices . Two binary searches (lower + upper bound) find this block in .

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

Level 5 — Mastery

Recall Solution L5.1

, . Order them: smaller rank (suffix 3), larger (suffix 1). Range is (i.e. from to ): . Direct check: , share ana (length 3). ✅ Why the min: walking from rank down to rank in sorted order, the shared prefix can only shrink or stay — each adjacent step removes at most the difference recorded in . The tightest bottleneck is the minimum. That min-over-a-range query is answered in by a sparse table after preprocessing.

Recall Solution L5.2

For : tails aa(0), a(1). Sorted: a(1) < aa(0), so . LCP(a,aa). Distinct . Check: distinct substrings of aa are a,aa2. ✅ For : (a<aa<aaa), . Distinct . Check: a,aa,aaa3. ✅ Insight: appending one repeated character adds exactly one new distinct substring here (the full new run), because every shorter run already existed. A string of identical letters has exactly distinct substrings.

Recall Solution L5.3

Let have predecessor in sorted order sharing characters. Since , dropping the first char gives and sharing characters, and crucially still sorts before (removing an equal leading char preserves order). The true predecessor of is at least as close as , so it shares at least chars. Hence starting the scan at never overshoots. Cost bound: increases by the number of while-loop character matches and decreases by at most per step. Over all steps total decrease , so total increase . Therefore the combined match work is — linear. ∎


Recall One-line recap of every level

L1 ::: Read (sorted tail indices) and invert it to ; string-end is smallest. L2 ::: One doubling round glues rank-pairs; Kasai walks by index carrying . L3 ::: Distinct ; the sentinel forces shorter suffixes first. L4 ::: = longest repeated substring; pattern hits form a contiguous block. L5 ::: LCP of any pair = range-min of ; Kasai is by the drop-by-one lemma.