3.8.7 · D5String Algorithms
Question bank — Suffix array — construction O(n log n), LCP array
True or false — justify
Every string of length has a suffix array that is a permutation of .
True — there are exactly suffixes, all start at distinct indices, and each appears once in sorted order, so is always a bijection on .
Two different suffixes of the same string can be exactly equal, so the sorted order has ties.
False — suffixes have different lengths (they start at different indices), so no two are identical; the shorter one is a strict prefix of the longer at most, and a prefix always sorts strictly before.
measures the shared prefix of suffixes starting at index and index .
False — it is the LCP of the sorted neighbours and ; the indices can be anywhere in the string, not and .
If for some , then and start with different first characters.
True — LCP means they share no leading character, so their first letters differ (this is exactly the boundary between "letter blocks" in ).
The rank array and the suffix array contain the same information.
True — rank is the inverse permutation of (), so either one reconstructs the other in ; they are two views of the same ordering.
Prefix-doubling needs exactly rounds regardless of the string.
True (up to ceiling) — doubles until , so it always takes rounds; the string's content changes the ranks but never the number of rounds.
Kasai's algorithm processes suffixes in sorted () order.
False — it walks indices in string order; that ordering is what makes the " drops by at most 1" reuse valid.
The number of distinct substrings of equals .
True — counts every prefix of every suffix (all substrings with multiplicity), and each subtracts exactly the substrings duplicated between sorted neighbours.
A suffix array plus an LCP array can answer "LCP of any two suffixes" (not just neighbours) in .
True — the LCP of and equals the minimum of , which a Sparse Table / RMQ answers in after preprocessing.
Spot the error
"To compare two suffixes on their first chars I need to compare characters directly."
Wrong — the whole point of doubling is you glue two known -rank halves: the key is the pair , so each comparison is , not .
"For I'll use since there's nothing there."
Wrong — is a valid rank, so a suffix that ran off the end would tie/outrank a real suffix; use (smaller than every real rank) to encode that end-of-string is lexicographically smallest.
"Each round of doubling with a comparison sort keeps the total at ."
Wrong — a comparison sort is per round over rounds ; only Radix Sort (or Counting Sort) per round gives true .
"In Kasai I reset at the start of every to be safe."
Wrong — that throws away the amortization and makes it ; you must carry across iterations, only doing before each step.
"After radix-sorting pairs I can reuse the same array in place while assigning ."
Wrong — you need old ranks to detect ties while assigning new ones; overwrite before all comparisons finish and equal pairs get different ranks, corrupting the order. Use a separate temp array.
" is the LCP of the first suffix with the last one."
Wrong — has no predecessor so it is defined as by convention; it is not a wrap-around comparison.
"Since suffixes overlap, binary-searching a pattern might match a non-contiguous set of positions."
Wrong — all suffixes with a given prefix are consecutive in sorted order, so occurrences of a pattern always form a contiguous block of (that's why Binary Search on Suffix Array works).
Why questions
Why does sorting by the pair correctly give the order by first characters?
Because the first chars of suffix split as (first of suffix ) followed by (first of suffix ); lexicographic order on that concatenation is exactly lexicographic order on the pair of their ranks.
Why is end-of-string treated as smaller than every real character?
Because a shorter suffix that is a prefix of a longer one must sort first (like "an" before "ana" in a dictionary), and modelling the missing character as the smallest possible value enforces exactly that rule.
Why does the LCP with the sorted predecessor drop by at most 1 when moving from index to ?
If shares chars with its predecessor , then and share and still sorts before , so the true predecessor of shares at least .
Why is a suffix array often preferred over a Suffix Tree despite the tree being "more powerful"?
The array is a flat integer array — far smaller constant-factor memory and much simpler to implement — giving most of the tree's query power (substring search, repeats, distinct counts) at a fraction of the pain.
Why do we need the rank array inside Kasai rather than just ?
We visit suffixes by starting index and must instantly find where sits in sorted order to get its predecessor ; only the inverse permutation rank gives that in .
Why does distinct-substring counting subtract exactly the LCP values?
Sorted neighbours share their first prefixes, so those substrings of the later suffix were already counted by the earlier one; subtracting them removes the double-counting precisely once.
Edge cases
What is the suffix array of a single-character string like "z"?
— there is exactly one suffix, and the LCP array is just (no adjacent pair exists to compare).
What does the LCP array look like for a string of all identical characters, e.g. "aaaa"?
Suffixes sort by length () and each neighbour shares everything but the extra leading char, so — the maximal staircase.
For a string of all distinct characters like "abcd", what is every LCP value?
All for — no two suffixes share a first character once sorted, so neighbours have no common prefix.
How many doubling rounds run when ?
Zero effective rounds beyond the initial single-character ranking, since already; the initial rank is the final order.
What happens in Kasai when (suffix is the smallest)?
It has no sorted predecessor, so we set and record nothing extra; the next simply starts fresh from this floor.
Does appending a unique sentinel character (like $, smaller than all letters) change the suffix array of the rest?
It adds one shortest suffix that sorts first and guarantees no suffix is a prefix of another, which simplifies proofs and some constructions without disturbing the relative order of the original suffixes.
Recall One-line self-test before you leave
Can you state, without peeking: (1) whose two suffixes compares, (2) why the off-end rank is , (3) why must be carried in Kasai, and (4) why doubling gives rounds? If any is fuzzy, reread that trap above.