Purpose: Karp-Miller-Rosenberg (1972) — the prefix doubling technique. Assign a canonical integer name to every substring of length , for every , in total. This single idea underlies suffix array construction, string comparison in , and a whole family of string DP tricks.
Algorithm
Let be the class label of the substring s[i .. i+2^k).
- = the character
s[i](or its rank in the alphabet). - For each : form the pair
for every , sort the pairs (radix sort in ), and let be the rank of ‘s pair. Two positions get the same label iff their length- substrings are identical. - Stop after rounds.
Code
// id[k][i] = class of s[i .. i+2^k), for all k. O(n log n) time and memory.
vector<vector<int>> kmr(const string& s) {
int n = s.size(), LOG = 1;
while ((1 << LOG) < n) LOG++;
vector<vector<int>> id(LOG + 1, vector<int>(n));
{ // level 0
vector<int> ord(n); iota(ord.begin(), ord.end(), 0);
sort(ord.begin(), ord.end(), [&](int a, int b){ return s[a] < s[b]; });
int c = 0;
for (int i = 0; i < n; i++) {
if (i && s[ord[i]] != s[ord[i-1]]) c++;
id[0][ord[i]] = c;
}
}
for (int k = 0; k < LOG; k++) {
int len = 1 << k;
vector<pair<pair<int,int>,int>> a(n);
for (int i = 0; i < n; i++)
a[i] = {{id[k][i], i + len < n ? id[k][i + len] : -1}, i};
sort(a.begin(), a.end());
int c = 0;
for (int i = 0; i < n; i++) {
if (i && a[i].first != a[i-1].first) c++;
id[k+1][a[i].second] = c;
}
}
return id;
}Complexity
- Time: with radix sort per level, with
std::sort - Space: if you keep every level; if you only need the last
What the table buys you
Once id[][] exists, compare any two substrings of equal length in : split the length into two overlapping powers of two,
with . This is a deterministic alternative to string hashing — no collision risk, no anti-hash tests, at the cost of memory.
Variants / Use Cases
- Suffix array construction — prefix doubling is KMR; the suffix array is just the final ordering
- substring comparison — for suffix ordering, lexicographic DP, and canonical forms without hashing
- Longest common extension (LCE) — binary search on length using equality tests, giving LCE
- Tandem repeats and periodicity queries
- SA-IS — the alternative when memory is too much
- Hashing — the randomised competitor: memory, compare, small failure probability