Intuition The ONE core idea
A string can be cut into all its tails (suffixes), and if you put those tails in dictionary order you unlock fast substring search, repeat-finding, and counting. Everything on the parent page is just two things: how to sort those tails quickly (the suffix array) and how much neighboring tails overlap (the LCP array).
This page builds every symbol the parent note uses, starting from "what is a string" and never using a piece of notation before it is drawn and explained. If a symbol below already feels obvious, skim it — but each later idea leans on the earlier ones, so the order matters.
Definition String, length, and character
A string s is just a row of symbols (letters) written left to right. We write its length as n = how many characters it has.
We name individual characters with a square-bracket index : s [ i ] is the character sitting at position i , and we count from 0 (0-indexed), so the first character is s [ 0 ] and the last is s [ n − 1 ] .
Picture a row of numbered boxes. Box number 0 holds the first letter.
Intuition Why 0-indexing and
s [ i ] ?
Every later formula (S A [ r ] , s [ i + h ] , r k [ i + k ] ) points into strings and arrays by position. If we don't agree that positions start at 0 and are written with square brackets, none of those formulas parse. This is the alphabet of the whole topic.
Why the topic needs it: the suffix array is a list of positions, so "position" must be rock solid.
suf ( i )
The suffix starting at i is the piece of the string from position i all the way to the end:
suf ( i ) = s [ i .. n − 1 ]
The little ".. " means "everything from the left number through the right number, inclusive."
If s = banana then suf ( 0 ) = banana (the whole thing), suf ( 3 ) = ana , and suf ( 5 ) = a (just the last letter). There are exactly n suffixes — one starting at each position.
Intuition Why cut into suffixes at all?
Here is the trick the whole chapter rests on: any substring is the beginning (prefix) of some suffix. Want to search for "ana" inside the word? "ana" starts suf ( 1 ) and suf ( 3 ) . So if we understand all suffixes, we understand all substrings for free.
Why the topic needs it: the suffix array sorts these objects.
Definition Prefix and longest common prefix (LCP)
A prefix of a string is a chunk taken from the front : the first few characters.
The longest common prefix of two strings is how many characters they share from the start before they first differ .
Line the two strings up left-aligned and read across until the first mismatch. The number of matching columns is the LCP length.
Worked example LCP by eye
anana vs ana : columns a , n , a match, then anana has n while ana has run out. So the LCP length is 3 .
Why the topic needs it: the entire LCP array (Section 3 of the parent) is just this number computed for neighboring sorted suffixes.
Definition Lexicographic order (
< on strings)
Two strings are compared like words in a dictionary: compare s [ 0 ] vs the other's first char; if equal move to the next; the first place they differ decides the order. If one string runs out first while matching, the shorter one is smaller (it comes first).
We reuse the ordinary alphabet order for single characters: a < b < c < … . In "banana" the only letters are a , b , n with a < b < n .
Common mistake "Longer strings are bigger."
Why it feels right: more letters, more value. Truth: length is irrelevant until you hit a mismatch. If everything matches and one ends, that shorter one is smaller — exactly like car comes before card in a dictionary. This "ran-out = smallest" rule is why the parent uses a − 1 sentinel for off-the-end positions.
Why the topic needs it: "sorted lexicographically" is the definition of the suffix array.
Intuition The end-of-string is the
smallest thing
When a suffix ends, imagine an invisible "nothing" character that beats every real letter (it is smaller than a ). That is precisely the − 1 the parent uses for r k [ i + k ] when i + k ≥ n . Same idea, two disguises.
S A and the letter r
Sort all n suffixes into dictionary order. The suffix array stores their starting positions in that sorted order.
We use r (for "rank position", r = 0 , 1 , … , n − 1 ) to index into the sorted list. So:
S A [ r ] = the starting index of the r -th smallest suffix.
Read it as: "go to shelf number r in the sorted bookshelf; which position did that tail start at?"
Why the topic needs it: S A is the output of construction. Everything downstream (binary search, LCP) reads it.
S A is a permutation
S A contains each of 0 , 1 , … , n − 1 exactly once — just shuffled into sorted order. It never invents or drops a position. That "reshuffle of the same numbers" property is what lets us invert it next.
rank [ i ]
S A answers "shelf r → which starting position?" The rank array answers the reverse question: "starting position i → which shelf is it on?"
rank [ S A [ r ]] = r
They are inverse permutations : apply one then the other and you land back where you started.
If S A = [ 5 , 3 , 1 , 0 , 4 , 2 ] then position 5 sits on shelf 0 , position 3 on shelf 1 , and so on, giving rank = [ 3 , 2 , 5 , 1 , 4 , 0 ] (read: position 0 is on shelf 3 ).
both directions
To sort we naturally produce S A (list positions in sorted order). But Kasai's LCP algorithm walks positions i = 0 , 1 , 2 , … and needs to ask "who sits just before me in sorted order?" — that requires jumping from a position to its shelf, i.e. rank . One array, read forwards; its inverse, read backwards.
Why the topic needs it: the predecessor lookup j = S A [ rank [ i ] − 1 ] literally chains both arrays.
k = "how many characters we currently sort by"
During construction we don't sort whole suffixes at once. We sort by their first k characters only. r k [ i ] = the rank of suffix i when we only look at its first k characters (ties allowed — equal first-k chunks get the same rank).
Start with k = 1 (sort by first letter alone), then k = 2 , k = 4 , k = 8 , doubling each round until k ≥ n , at which point "first k chars" already covers the whole suffix.
Intuition Why doubling, and why a
pair ?
The first 2 k characters of suffix i = (first k chars of suffix i ) glued to (first k chars of suffix i + k ). We already computed ranks for both halves in the previous round! So instead of re-reading 2 k letters, we compare the pair ( r k [ i ] , r k [ i + k ]) . Comparing pairs of small integers is cheap; that is the whole speed trick.
Why the topic needs it: this is the O ( n log n ) engine. The log n is the number of doublings.
O ( f ( n )) — "grows no faster than"
O ( f ( n )) is a promise about how the running time scales as the input n gets big, ignoring constant factors. O ( n ) = "does a fixed amount of work per character." O ( n log n ) = "log n passes, each linear."
log n (base 2)
log 2 n = how many times you can halve n before reaching 1 — equivalently, how many times you double starting from 1 before you reach n . For n = 8 that's 3 (1 → 2 → 4 → 8 ).
Why the topic needs it: the number of doubling rounds is exactly ⌈ log 2 n ⌉ , so the cost label O ( n log n ) names "linear work per doubling round × number of rounds."
h = current overlap length being carried across steps
In Kasai's algorithm h holds "how many characters suffix i shares with its sorted predecessor." We carry h over to the next position, only shrinking it by 1, because of the "LCP drops by at most 1" lemma (parent Section 3).
Why the topic needs it: h is why LCP construction is O ( n ) instead of O ( n 2 ) ; without carrying h you re-scan everything.
Characters s at i (0-indexed)
Character order a lt b lt n
Lexicographic order of strings
Self-test: can you answer each before revealing?
What does s [ i ] mean and where does counting start? The character at position i , counting from 0 , so s [ 0 ] is the first and s [ n − 1 ] the last.
What is suf ( i ) ? The tail of s from position i to the end, s [ i .. n − 1 ] ; there are n of them.
Why does every substring relate to suffixes? Every substring is a prefix (front chunk) of some suffix, so studying suffixes covers all substrings.
How do you compare two strings lexicographically? Compare character by character from the left; the first mismatch decides; if one runs out while matching, the shorter one is smaller.
What is S A [ r ] in words? The starting index of the r -th smallest suffix in dictionary order.
How are S A and rank related? They are inverse permutations: rank [ S A [ r ]] = r ; S A maps shelf→position, rank maps position→shelf.
What is r k [ i ] and why doubling? Rank of suffix i using only its first k chars; doubling lets the first 2 k chars be compared as the pair ( r k [ i ] , r k [ i + k ]) using already-known ranks.
Why is − 1 used when i + k ≥ n ? A suffix that ran off the end acts like an empty/smallest character, so it must sort before any real character — encoded as − 1 .
What does O ( n log n ) mean here? About log 2 n doubling rounds, each doing linear (O ( n ) ) work.
What does h do in Kasai's algorithm? Carries the current overlap length across positions, shrinking by at most 1 per step, giving linear-time LCP.