Pattern Reference
Suffix Array
"Build suffix array, LCP array, longest repeated substring, number of distinct substrings, longest common substring."
Loading...
Deep Dive Tutorial
A suffix array SA[] is a sorted list of starting indices of all suffixes. For "banana": suffixes sorted = [a, ana, anana, banana, na, nana] → SA = [5,3,1,0,4,2]. The LCP array stores longest common prefix between SA[i] and SA[i-1] suffixes. Together they compress all substring information into two arrays, enabling many string operations in O(n) or O(n log n).
Suffix array (O(n log²n)) + Kasai LCP algorithm
// O(n log²n) suffix array construction
function buildSA(s) {
const n = s.length;
let sa = Array.from({length: n}, (_, i) => i);
let rank = [...s].map(c => c.charCodeAt(0));
let tmp = new Array(n);
for (let gap = 1; gap < n; gap <<= 1) {
const cmp = (a, b) => {
if (rank[a] !== rank[b]) return rank[a] - rank[b];
const ra = a + gap < n ? rank[a + gap] : -1;
const rb = b + gap < n ? rank[b + gap] : -1;
return ra - rb;
};
sa.sort(cmp);
tmp[sa[0]] = 0;
for (let i = 1; i < n; i++)
tmp[sa[i]] = tmp[sa[i-1]] + (cmp(sa[i-1], sa[i]) < 0 ? 1 : 0);
rank = [...tmp];
if (rank[sa[n-1]] === n-1) break;
}
return sa;
}
// Kasai's algorithm: build LCP array in O(n)
function buildLCP(s, sa) {
const n = s.length;
const rank = new Array(n);
for (let i = 0; i < n; i++) rank[sa[i]] = i;
const lcp = new Array(n).fill(0);
let h = 0;
for (let i = 0; i < n; i++) {
if (rank[i] > 0) {
let j = sa[rank[i] - 1];
while (i + h < n && j + h < n && s[i + h] === s[j + h]) h++;
lcp[rank[i]] = h;
if (h > 0) h--;
}
}
return lcp; // lcp[i] = LCP(sa[i-1], sa[i])
}
// Count distinct substrings = total substrings - sum(lcp)
// = n*(n+1)/2 - sum(lcp array)Worked Problems
text
Suffix array key facts:
- SA[i] = starting index of the i-th smallest suffix
- LCP[i] = length of longest common prefix between SA[i-1] and SA[i]
- Distinct substrings = n*(n+1)/2 - sum(LCP)
- Longest repeated substring = max(LCP)
- Pattern search: binary search in SA for pattern match range
Construction complexity:
- Naive sort: O(n² log n)
- O(n log²n): sort with doubled comparison
- O(n log n): radix sort variant (SA-IS: O(n))
vs Rolling Hash: Suffix array gives exact answers, no collision risk. Harder to code but more powerful for multi-query scenarios.
- SA[i] = starting index of the i-th smallest suffix
- LCP[i] = length of longest common prefix between SA[i-1] and SA[i]
- Distinct substrings = n*(n+1)/2 - sum(LCP)
- Longest repeated substring = max(LCP)
- Pattern search: binary search in SA for pattern match range
Construction complexity:
- Naive sort: O(n² log n)
- O(n log²n): sort with doubled comparison
- O(n log n): radix sort variant (SA-IS: O(n))
vs Rolling Hash: Suffix array gives exact answers, no collision risk. Harder to code but more powerful for multi-query scenarios.