Algorithm Search⌘K

Proofs: Lyndon array

These are the human proofs of Lyndon array, at full length.

LYNDON-ARRAY computes the longest Lyndon word at each index full

This proof shows: correctness, termination.

Notation. s=s[0..n−1]s = s[0..n-1] is a word over a totally ordered alphabet, with 0-based indices. Words are compared in lexicographic order, where a proper prefix is smaller, and s[i..]s[i..] is the suffix that starts at ii. The array rankrank is the inverse suffix array: rank[i]<rank[j]rank[i] < rank[j] if and only if s[i..]<s[j..]s[i..] < s[j..], and all ranks are distinct. For 0≤i<n0 \le i < n, nss(i)\mathrm{nss}(i) is the least j>ij > i with rank[j]<rank[i]rank[j] < rank[i], or nn if there is none. We use two results from the Lyndon factorization and Chen-Fox-Lyndon theorem pages: (1) the first factor of the Lyndon factorization of a nonempty word is its longest Lyndon prefix; (2) the start indices of the factors of a word ww are exactly the indices ii such that w[i..]<w[j..]w[i..] < w[j..] for every j<ij < i.

Lemma 1. For each ii, the longest Lyndon word that starts at ii has length nss(i)−i\mathrm{nss}(i) - i.

Proof. Let w=s[i..n−1]w = s[i..n-1]. A Lyndon word starting at ii is a Lyndon prefix of ww, so by (1) the longest one is the first factor of the factorization of ww, and its length is the start of the second factor (or ∣w∣|w| if there is one factor). The suffixes of ww are w[d..]=s[i+d..]w[d..] = s[i+d..], compared as suffixes of ss. By (2), the second factor of ww starts at the least d≥1d \ge 1 such that w[d..]<w[e..]w[d..] < w[e..] for all e<de < d. Let d∗d^* be the least d≥1d \ge 1 with w[d..]<w[0..]w[d..] < w[0..], or ∣w∣|w| if there is none. For 1≤e<d∗1 \le e < d^*, w[e..]>w[0..]w[e..] > w[0..], so the minimum of w[0..],…,w[e−1..]w[0..], \ldots, w[e-1..] is w[0..]w[0..] for each e≤d∗e \le d^*. Hence an index 1≤e<d∗1 \le e < d^* is not a factor start, because w[e..]>w[0..]w[e..] > w[0..], and d∗d^* is a factor start if d∗<∣w∣d^* < |w|, because w[d∗..]w[d^*..] is smaller than the minimum w[0..]w[0..]. So the second factor starts at d∗d^*, and i+d∗=nss(i)i + d^* = \mathrm{nss}(i). If d∗=∣w∣d^* = |w|, then ww has one factor, of length n−i=nss(i)−in - i = \mathrm{nss}(i) - i. □\square

Stack invariant. At the start of the iteration of line L3 for ii (and, for i=−1i = -1, at the end of the loop), the stack SS holds exactly the set

Ti={ j:i<j<n and rank[j]<rank[j′] for every j′ with i<j′<j },T_i = \{\, j : i < j < n \text{ and } rank[j] < rank[j'] \text{ for every } j' \text{ with } i < j' < j \,\},

ordered with the smallest index on top. For j<j′j < j' both in TiT_i, the definition with j′j' gives rank[j′]<rank[j]rank[j'] < rank[j], so the ranks decrease from the top down. Also λ[i′]=nss(i′)−i′\lambda[i'] = \mathrm{nss}(i') - i' for every i′>ii' > i.

Initialization. For i=n−1i = n - 1, Tn−1T_{n-1} is empty, and so is the stack.

Maintenance. First, nss(i)∈Ti\mathrm{nss}(i) \in T_i when nss(i)<n\mathrm{nss}(i) < n: every j′j' strictly between ii and nss(i)\mathrm{nss}(i) has rank[j′]>rank[i]>rank[nss(i)]rank[j'] > rank[i] > rank[\mathrm{nss}(i)]. Lines L4 and L5 pop the elements of rank larger than rank[i]rank[i]. Since ranks decrease from the top down, these form a top segment of the stack, and the pops stop at the first element with a smaller rank, or at the empty stack. Each element jj of TiT_i with j<nss(i)j < \mathrm{nss}(i) has rank[j]>rank[i]rank[j] > rank[i], by the choice of nss(i)\mathrm{nss}(i), and is popped; nss(i)\mathrm{nss}(i) itself has a smaller rank and stops the pops. So after the pops the top is nss(i)\mathrm{nss}(i), or the stack is empty and nss(i)=n\mathrm{nss}(i) = n. Lines L6 and L7 set λ[i]=nss(i)−i\lambda[i] = \mathrm{nss}(i) - i. After line L8, the stack holds ii on top of the elements of TiT_i with rank smaller than rank[i]rank[i]. This set is Ti−1T_{i-1}: an index j>ij > i is in Ti−1T_{i-1} exactly when it is in TiT_i and rank[j]<rank[i]rank[j] < rank[i], and ii is in Ti−1T_{i-1} with nothing between. The smallest index, ii, is on top.

Termination. The loop runs nn times; each pop removes an element pushed once, so there are at most nn pops in total.

Conclusion for LYNDON-ARRAY. At the end, λ[i]=nss(i)−i\lambda[i] = \mathrm{nss}(i) - i for every ii, which by Lemma 1 is the length of the longest Lyndon word that starts at ii.

FACTORS. By (1), the first factor of the factorization of s[i..]s[i..] is s[i..i+λ[i]−1]s[i..i+\lambda[i]-1]. If s[i..]=ℓ1ℓ2⋯ℓks[i..] = \ell_1 \ell_2 \cdots \ell_k is the factorization, then ℓ2⋯ℓk\ell_2 \cdots \ell_k is a nonincreasing product of Lyndon words, so by uniqueness it is the factorization of s[i+λ[i]..]s[i + \lambda[i]..]. By induction on n−in - i, lines L12 and L13 list the start indices of all the factors, and the loop stops because λ[i]≥1\lambda[i] \ge 1. ■\blacksquare

Back to Lyndon array