String Algorithms: KMP, Z, Suffix Array
KMP, Z, Suffix Array
1. The Pattern-Matching Problem
Given text T (length n) and pattern P (length m), find all occurrences of P in T. Naive: O(n·m). KMP, Z, and Rabin-Karp do it in O(n + m). Suffix arrays handle a wider family of substring queries in O(n log² n) or O(n log n) preprocessing.
2. KMP Failure Function
For pattern P, compute fail[i] = length of the longest proper prefix of
P[0..i] that is also a suffix. When matching against T, on a mismatch at P[j] we don't
restart — we jump to fail[j-1]. Total comparisons O(n + m).
#include <bits/stdc++.h>
using namespace std;
vector<int> buildFail(const string& p) {
int m = p.size();
vector<int> fail(m, 0);
int k = 0;
for (int i = 1; i < m; i++) {
while (k > 0 && p[k] != p[i]) k = fail[k - 1];
if (p[k] == p[i]) k++;
fail[i] = k;
}
return fail;
}
vector<int> kmpSearch(const string& t, const string& p) {
vector<int> fail = buildFail(p), out;
int k = 0, n = t.size(), m = p.size();
for (int i = 0; i < n; i++) {
while (k > 0 && p[k] != t[i]) k = fail[k - 1];
if (p[k] == t[i]) k++;
if (k == m) { out.push_back(i - m + 1); k = fail[k - 1]; }
}
return out;
}
int main() {
for (int i : kmpSearch("ABABCABABABCABABABA", "ABABCAB"))
cout << "match at " << i << "\n";
}
3. Z-Algorithm
For string S, z[i] = length of the longest substring starting at i that
matches a prefix of S. Compute Z in O(n) using a sliding "Z-box". Pattern matching
in T: build Z of P + '#' + T; positions where Z = |P| are matches.
4. Rabin-Karp Rolling Hash
Compute a polynomial hash of P. Slide a window over T, updating the hash in O(1) per shift. On hash match, verify char-by-char. Expected O(n + m). Use two independent hashes (two primes) for collision safety.
5. Suffix Array
Sort all n suffixes of S lexicographically; store their starting indices in sa[0..n−1].
Build in O(n log² n) with std::sort + comparator, or O(n log n) with the
doubling algorithm. With the LCP array (longest common prefix between consecutive
suffixes), suffix array answers many substring problems linearly.
#include <bits/stdc++.h>
using namespace std;
vector<int> buildSA(const string& s) {
int n = s.size();
vector<int> sa(n), rk(n), tmp(n);
for (int i = 0; i < n; i++) { sa[i] = i; rk[i] = s[i]; }
for (int k = 1; k < n; k *= 2) {
auto cmp = [&](int a, int b) {
if (rk[a] != rk[b]) return rk[a] < rk[b];
int ra = a + k < n ? rk[a + k] : -1;
int rb = b + k < n ? rk[b + k] : -1;
return ra < rb;
};
sort(sa.begin(), sa.end(), cmp);
tmp[sa[0]] = 0;
for (int i = 1; i < n; i++)
tmp[sa[i]] = tmp[sa[i - 1]] + (cmp(sa[i - 1], sa[i]) ? 1 : 0);
rk = tmp;
if (rk[sa[n - 1]] == n - 1) break;
}
return sa;
}
int main() {
string s = "banana";
for (int i : buildSA(s))
cout << i << ": " << s.substr(i) << "\n";
}
6. Aho-Corasick (Preview)
For multi-pattern search (find all occurrences of any pattern from a set in a single text): build a trie of patterns, then add "failure links" similar to KMP. One pass of T finds every match across all patterns in O(|T| + matches). Used by spam filters, virus scanners, string-database lookups.
7. Practice Problems
-
Longest happy prefix — longest proper prefix of S that is also a suffix.Longest happy prefix।
✨ Show Answer (উত্তর দেখুন)
Approach: compute KMP failure function. Answer = S.substr(0, fail[n−1]).
-
Shortest palindrome — prepend the fewest chars to make S a palindrome.Shortest palindrome।
✨ Show Answer (উত্তর দেখুন)
Approach: let R = reverse(S). Compute KMP fail of S + '#' + R. fail[last] tells you the longest prefix of S that matches a suffix of R — that is the longest palindromic prefix. Prepend reverse of the rest.
-
Repeated string match — given A, B, find min copies of A so that B is a substring.Repeated string match।
✨ Show Answer (উত্তর দেখুন)
Approach: repeat A until length ≥ |B|; check if B is a substring (KMP). If not, append one more copy and check again. If still no, return −1.
-
Longest substring repeated ≥ 2 times — solve via suffix array + LCP.Repeated substring — suffix array + LCP।
✨ Show Answer (উত্তর দেখুন)
Approach: build SA + LCP. Answer = max(LCP[i]) — the longest LCP between consecutive sorted suffixes is the longest repeated substring.
-
Count distinct substrings of S using suffix array.Distinct substring গুনা।
✨ Show Answer (উত্তর দেখুন)
Approach: total substrings = n(n+1)/2. Subtract Σ LCP[i] (the number of duplicate prefixes). Answer = n(n+1)/2 − Σ LCP.
-
Use Rabin-Karp double hashing to find a 5-character pattern in a 10⁵-character text.Double hashing দিয়ে pattern search।
✨ Show Answer (উত্তর দেখুন)
Approach: two independent rolling hashes (mod 1e9+7 and 998244353). Match if both equal. Probability of collision ≈ 1/(10¹⁸).
Summary — Module 38
KMP and Z give linear-time pattern matching with failure functions. Rabin-Karp uses rolling hashes (always double-hash). Suffix arrays + LCP unlock most substring queries in O(n log n) preprocessing. Aho-Corasick handles multi-pattern in one pass. Together, this is the entire string-matching toolbox.