Put the four tails in dictionary order. Compare letter by letter:
ab (from index 2) vs abab (index 0): they agree on ab, then abab continues while abends. A string that ends is smaller (shorter prefix wins). So ab<abab.
Both a-starting tails come before both b-starting tails.
Among b: b (index 3) vs bab (index 1): b ends first → smaller. So b<bab.
Sorted: ab(2)<abab(0)<b(3)<bab(1).
SA=[2,0,3,1]
Recall Solution L1.2
rank is the inverse permutation: rank[SA[r]]=r. Walk r=0..3:
Sort pairs ascending (compare first component, then second):
(0,−1)→i=2, (0,0)→i=0, (1,−1)→i=3, (1,1)→i=1.
SA=[2,0,3,1]
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 O(n) per round.)
Recall Solution L2.2
Walk by starting index i, predecessor j=SA[rank[i]−1], extend h while s[i+h]=s[j+h], then h←max(h−1,0):
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, LCP[0]=0 by convention:
LCP=[0,2,0,1]
Reading it: neighbors SA[0]=2,SA[1]=0 (ab,abab) share 2; SA[1]=0,SA[2]=3 (abab,b) share 0; SA[2]=3,SA[3]=1 (b,bab) share 1. ✅
Total prefixes over all suffixes: ∑(n−SA[r])=(4−2)+(4−0)+(4−3)+(4−1)=2+4+1+3=10.
Overlaps double-counted: ∑r≥1LCP[r]=2+0+1=3.
Distinct =10−3=7.
Sanity list: length-1 a,b; length-2 ab,ba; length-3 aba,bab; length-4 abab. That is 2+2+2+1=7. ✅
Recall Solution L3.2
Suffix i 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 rk[i+k]=−1 and keeps rk[j+k]=0. Since −1<0, the pair (⋅,−1) sorts before (⋅,0), placing suffix i first. ✅ Correct.
If we had used 0 (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
k runs 1,2,4,8,…. We stop when k≥1000: 29=512<1000≤1024=210.
So we need k=1024, reached after 10 doublings from k=1 (1→2→4→8→16→32→64→128→256→512→1024).
This is ⌈log2n⌉=⌈log21000⌉=10. Each round is O(n) with radix sort → total O(nlogn).
maxLCP=3 at r=2. So the longest repeated substring has length 3, starting at SA[2]=1:
s[1..3]="ana".
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.
Recall Solution L4.2
Because SA 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 r=4 (na) and r=5 (nana). Block =[4,5], so na occurs 2 times, at positions SA[4]=4 and SA[2’s row]=2, i.e. indices {4,2}. Two binary searches (lower + upper bound) find this block in O(∣p∣logn).
rank[1]=2, rank[3]=1. Order them: smaller rank =1 (suffix 3), larger =2 (suffix 1).
Range is r∈{2} (i.e. r from 1+1 to 2): min{LCP[2]}=min{3}=3.
Direct check: suf(1)=anana, suf(3)=ana share ana (length 3). ✅
Why the min: walking from rank a down to rank b in sorted order, the shared prefix can only shrink or stay — each adjacent step removes at most the difference recorded in LCP. The tightest bottleneck is the minimum. That min-over-a-range query is answered in O(1) by a sparse table after O(nlogn) preprocessing.
Recall Solution L5.2
For s="aa": tails aa(0), a(1). Sorted: a(1) < aa(0), so SA=[1,0].
LCP[1]= LCP(a,aa)=1. Distinct =∑(n−SA[r])−∑LCP=[(2−1)+(2−0)]−1=(1+2)−1=2.
Check: distinct substrings of aa are a,aa → 2. ✅
For s="aaa": SA=[2,1,0] (a<aa<aaa), LCP=[0,1,2].
Distinct =[(3−2)+(3−1)+(3−0)]−(1+2)=(1+2+3)−3=6−3=3. Check: a,aa,aaa → 3. ✅
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 m identical letters has exactly m distinct substrings.
Recall Solution L5.3
Let suf(i) have predecessor suf(j) in sorted order sharing h≥1 characters. Since s[i]=s[j], dropping the first char gives suf(i+1) and suf(j+1) sharing h−1 characters, and crucially suf(j+1) still sorts beforesuf(i+1) (removing an equal leading char preserves order). The true predecessor of suf(i+1) is at least as close as suf(j+1), so it shares at leasth−1 chars. Hence starting the scan at h−1 never overshoots.
Cost bound:h increases by the number of while-loop character matches and decreases by at most 1 per step. Over all n steps total decrease ≤n, so total increase ≤n+hfinal≤2n. Therefore the combined match work is O(n) — linear. ∎
Recall One-line recap of every level
L1 ::: Read SA (sorted tail indices) and invert it to rank; string-end is smallest.
L2 ::: One doubling round glues rank-pairs; Kasai walks by index carrying h.
L3 ::: Distinct =∑(n−SA[r])−∑LCP; the −1 sentinel forces shorter suffixes first.
L4 ::: maxLCP = longest repeated substring; pattern hits form a contiguous SA block.
L5 ::: LCP of any pair = range-min of LCP; Kasai is O(n) by the drop-by-one lemma.