Chapter 41: KMP Algorithm
The Knuth-Morris-Pratt (KMP) algorithm is a linear-time string matching algorithm. While the naive approach to finding a pattern in a text takes O(n·m) time (where n is the text length and m is the pattern length), KMP achieves O(n + m) by preprocessing the pattern to avoid redundant comparisons. The key insight is the failure function (also called the LPS — Longest Proper Prefix which is also Suffix — array), which tells us exactly how far back we can safely skip when a mismatch occurs.
41.1 The Failure Function (LPS Array)
41.1.1 Definition
The LPS array (Longest Proper Prefix which is also Suffix) for a pattern p of length m is an array lps[0..m-1] where lps[i] is the length of the longest proper prefix of p[0..i] which is also a suffix of p[0..i].
“Proper” means the prefix cannot be the entire string itself.
Example: For pattern = “ABABAC”:
| i | p[0..i] | Proper Prefixes | Proper Suffixes | Longest Match | lps[i] |
|---|---|---|---|---|---|
| 0 | A | {} | {} | none | 0 |
| 1 | AB | {A} | {B} | none | 0 |
| 2 | ABA | {A, AB} | {BA, A} | A | 1 |
| 3 | ABAB | {A, AB, ABA} | {BAB, AB, B} | AB | 2 |
| 4 | ABABA | {A, AB, ABA, ABAB} | {BABA, ABA, BA, A} | ABA | 3 |
| 5 | ABABAC | {A, AB, ABA, ABAB, ABABA} | {BABAC, ABAC, BAC, AC, C} | none | 0 |
So lps = [0, 0, 1, 2, 3, 0].
41.1.2 What It Represents
When we’re matching the pattern against the text and a mismatch occurs at position j in the pattern, lps[j-1] tells us the next position in the pattern to try — we don’t need to go all the way back to the beginning. The characters before lps[j-1] are guaranteed to match because they’re a prefix that matches the suffix we’ve already seen.
41.2 Building the LPS Array
41.2.1 Algorithm
#include <iostream>
#include <vector>
#include <string>
using namespace std;
vector<int> buildLPS(const string& pattern) {
int m = pattern.size();
vector<int> lps(m, 0);
int len = 0; // length of the previous longest prefix-suffix
int i = 1;
while (i < m) {
if (pattern[i] == pattern[len]) {
len++;
lps[i] = len;
i++;
} else {
if (len != 0) {
// Fall back to the previous longest prefix-suffix
len = lps[len - 1];
// Don't increment i — try again with the new len
} else {
lps[i] = 0;
i++;
}
}
}
return lps;
}
int main() {
string pattern = "ABABAC";
auto lps = buildLPS(pattern);
cout << "Pattern: " << pattern << endl;
cout << "LPS: ";
for (int x : lps) cout << x << " ";
cout << endl; // 0 0 1 2 3 0
string pattern2 = "AABAACAABAA";
auto lps2 = buildLPS(pattern2);
cout << "Pattern: " << pattern2 << endl;
cout << "LPS: ";
for (int x : lps2) cout << x << " ";
cout << endl; // 0 1 0 1 2 0 1 2 3 4 5
string pattern3 = "AAACAAAAAC";
auto lps3 = buildLPS(pattern3);
cout << "Pattern: " << pattern3 << endl;
cout << "LPS: ";
for (int x : lps3) cout << x << " ";
cout << endl; // 0 1 2 0 1 2 3 3 3 4
return 0;
}
41.2.2 Dry Run: Building LPS for “ABABAC”
pattern = "ABABAC"
lps = [0, 0, 0, 0, 0, 0]
len = 0, i = 1
i=1: pattern[1]='B' vs pattern[len=0]='A' → mismatch, len==0 → lps[1]=0, i=2
lps = [0, 0, 0, 0, 0, 0]
i=2: pattern[2]='A' vs pattern[len=0]='A' → match! len=1, lps[2]=1, i=3
lps = [0, 0, 1, 0, 0, 0]
i=3: pattern[3]='B' vs pattern[len=1]='B' → match! len=2, lps[3]=2, i=4
lps = [0, 0, 1, 2, 0, 0]
i=4: pattern[4]='A' vs pattern[len=2]='A' → match! len=3, lps[4]=3, i=5
lps = [0, 0, 1, 2, 3, 0]
i=5: pattern[5]='C' vs pattern[len=3]='B' → mismatch, len!=0 → len=lps[3-1]=lps[2]=1
Try again: pattern[5]='C' vs pattern[len=1]='B' → mismatch, len!=0 → len=lps[1-1]=lps[0]=0
Try again: pattern[5]='C' vs pattern[len=0]='A' → mismatch, len==0 → lps[5]=0, i=6
lps = [0, 0, 1, 2, 3, 0]
Done!
Complexity: O(m) time, O(m) space. The key to the O(m) complexity is that len only increases by 1 per iteration of i, and when it decreases, it decreases from a previously increased value. The total number of decreases cannot exceed the total number of increases, which is bounded by m.
41.2.3 Dry Run: Building LPS for “AAACAAAAAC”
pattern = "AAACAAAAAC"
lps = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
len=0, i=1
i=1: 'A'=='A' → len=1, lps[1]=1, i=2. lps=[0,1,0,0,0,0,0,0,0,0]
i=2: 'A'=='A' → len=2, lps[2]=2, i=3. lps=[0,1,2,0,0,0,0,0,0,0]
i=3: 'C'!='A' → len=lps[1]=1. Try: 'C'!='A' → len=lps[0]=0. Try: 'C'!='A' → lps[3]=0, i=4.
lps=[0,1,2,0,0,0,0,0,0,0]
i=4: 'A'=='A' → len=1, lps[4]=1, i=5. lps=[0,1,2,0,1,0,0,0,0,0]
i=5: 'A'=='A' → len=2, lps[5]=2, i=6. lps=[0,1,2,0,1,2,0,0,0,0]
i=6: 'A'=='A' → len=3, lps[6]=3, i=7. lps=[0,1,2,0,1,2,3,0,0,0]
i=7: 'A'!='C' → len=lps[2]=2. Try: 'A'=='A' → len=3, lps[7]=3, i=8.
lps=[0,1,2,0,1,2,3,3,0,0]
i=8: 'A'!='C' → len=lps[2]=2. Try: 'A'=='A' → len=3, lps[8]=3, i=9.
lps=[0,1,2,0,1,2,3,3,3,0]
i=9: 'C'=='C' → len=4, lps[9]=4, i=10.
lps=[0,1,2,0,1,2,3,3,3,4]
Done! lps = [0, 1, 2, 0, 1, 2, 3, 3, 3, 4]
41.3 KMP Search
41.3.1 The Algorithm
#include <iostream>
#include <vector>
#include <string>
using namespace std;
vector<int> buildLPS(const string& pattern) {
int m = pattern.size();
vector<int> lps(m, 0);
int len = 0, i = 1;
while (i < m) {
if (pattern[i] == pattern[len]) {
len++;
lps[i] = len;
i++;
} else {
if (len != 0) {
len = lps[len - 1];
} else {
lps[i] = 0;
i++;
}
}
}
return lps;
}
// Returns all starting indices where pattern occurs in text
vector<int> kmpSearch(const string& text, const string& pattern) {
vector<int> result;
int n = text.size(), m = pattern.size();
if (m == 0) return result;
if (m > n) return result;
vector<int> lps = buildLPS(pattern);
int i = 0; // index for text
int j = 0; // index for pattern
while (i < n) {
if (text[i] == pattern[j]) {
i++;
j++;
}
if (j == m) {
// Found a match at index i - j
result.push_back(i - j);
j = lps[j - 1]; // continue searching for more matches
} else if (i < n && text[i] != pattern[j]) {
if (j != 0) {
j = lps[j - 1]; // use the LPS to skip
} else {
i++; // no prefix to fall back to, advance text
}
}
}
return result;
}
int main() {
string text1 = "ABABDABACDABABCABAB";
string pattern1 = "ABABCABAB";
auto matches = kmpSearch(text1, pattern1);
cout << "Text: " << text1 << endl;
cout << "Pattern: " << pattern1 << endl;
cout << "Found at indices: ";
for (int idx : matches) cout << idx << " ";
cout << endl; // 9
string text2 = "AAAAABAAABA";
string pattern2 = "AAAB";
auto matches2 = kmpSearch(text2, pattern2);
cout << "Found at indices: ";
for (int idx : matches2) cout << idx << " ";
cout << endl; // 1 7
string text3 = "ABABABAB";
string pattern3 = "ABAB";
auto matches3 = kmpSearch(text3, pattern3);
cout << "Found at indices: ";
for (int idx : matches3) cout << idx << " ";
cout << endl; // 0 2 4
return 0;
}
41.3.2 Dry Run: KMP Search for text = “ABABDABACDABABCABAB”, pattern = “ABABCABAB”
First, build LPS for “ABABCABAB”:
lps = [0, 0, 1, 2, 0, 1, 2, 3, 4]
Now the search:
text = A B A B D A B A C D A B A B C A B A B
pattern = A B A B C A B A B
lps = 0 0 1 2 0 1 2 3 4
i=0, j=0: text[0]='A' == pattern[0]='A' → i=1, j=1
i=1, j=1: text[1]='B' == pattern[1]='B' → i=2, j=2
i=2, j=2: text[2]='A' == pattern[2]='A' → i=3, j=3
i=3, j=3: text[3]='B' == pattern[3]='B' → i=4, j=4
i=4, j=4: text[4]='D' != pattern[4]='C' → j=lps[3]=2
i=4, j=2: text[4]='D' != pattern[2]='A' → j=lps[1]=0
i=4, j=0: text[4]='D' != pattern[0]='A' → i=5
i=5, j=0: text[5]='A' == pattern[0]='A' → i=6, j=1
i=6, j=1: text[6]='B' == pattern[1]='B' → i=7, j=2
i=7, j=2: text[7]='A' == pattern[2]='A' → i=8, j=3
i=8, j=3: text[8]='C' != pattern[3]='B' → j=lps[2]=1
i=8, j=1: text[8]='C' != pattern[1]='B' → j=lps[0]=0
i=8, j=0: text[8]='C' != pattern[0]='A' → i=9
i=9, j=0: text[9]='D' != pattern[0]='A' → i=10
i=10, j=0: text[10]='A' == pattern[0]='A' → i=11, j=1
i=11, j=1: text[11]='B' == pattern[1]='B' → i=12, j=2
i=12, j=2: text[12]='A' == pattern[2]='A' → i=13, j=3
i=13, j=3: text[13]='B' == pattern[3]='B' → i=14, j=4
i=14, j=4: text[14]='C' == pattern[4]='C' → i=15, j=5
i=15, j=5: text[15]='A' == pattern[5]='A' → i=16, j=6
i=16, j=6: text[16]='B' == pattern[6]='B' → i=17, j=7
i=17, j=7: text[17]='A' == pattern[7]='A' → i=18, j=8
i=18, j=8: text[18]='B' == pattern[8]='B' → i=19, j=9
j==9==m → MATCH at index 19-9 = 10
j = lps[8] = 4
i=19, j=4: i==n → done
Result: [10]
Complexity: O(n + m) time. Each character in the text is examined at most once (i only increases), and each pattern position j is decreased at most as many times as it was increased. The LPS construction is O(m). Total: O(n + m). Space: O(m) for the LPS array.
41.4 Applications
41.4.1 Find All Occurrences
Already demonstrated above. The KMP search naturally finds all occurrences by continuing after each match.
41.4.2 Repeated Substring Pattern (LeetCode 459)
Given a string, check if it can be constructed by taking a substring and appending multiple copies of it.
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Solution {
public:
bool repeatedSubstringPattern(string s) {
int n = s.size();
// Build LPS array
vector<int> lps(n, 0);
int len = 0, i = 1;
while (i < n) {
if (s[i] == s[len]) {
len++;
lps[i] = len;
i++;
} else {
if (len != 0) {
len = lps[len - 1];
} else {
lps[i] = 0;
i++;
}
}
}
// The key insight:
// If the string is made of repeated substrings, then
// lps[n-1] gives us the length of the longest proper prefix-suffix.
// The substring length would be n - lps[n-1].
// If n is divisible by (n - lps[n-1]), the string is repeated.
int longestPrefixSuffix = lps[n - 1];
int candidateLen = n - longestPrefixSuffix;
return longestPrefixSuffix > 0 && n % candidateLen == 0;
}
};
int main() {
Solution sol;
cout << boolalpha;
cout << sol.repeatedSubstringPattern("abab") << endl; // true
cout << sol.repeatedSubstringPattern("aba") << endl; // false
cout << sol.repeatedSubstringPattern("abcabcabcabc") << endl; // true
cout << sol.repeatedSubstringPattern("a") << endl; // false
cout << sol.repeatedSubstringPattern("ababab") << endl; // true
return 0;
}
Why this works: If a string s of length n is formed by repeating a substring of length d, then:
- The LPS value at the last position
lps[n-1]must ben - d(the longest proper prefix that’s also a suffix covers all but one copy of the repeating unit). nmust be divisible byd = n - lps[n-1].
Complexity: O(n) time, O(n) space.
41.4.3 Shortest Palindrome (LeetCode 215)
Find the shortest palindrome by adding characters in front.
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
string shortestPalindrome(string s) {
// The idea: find the longest palindromic prefix of s.
// Then we only need to add the reverse of the remaining suffix in front.
//
// Trick: use KMP on (s + "#" + reverse(s))
// The LPS value at the last position tells us the length of the
// longest palindromic prefix.
string rev = s;
reverse(rev.begin(), rev.end());
string combined = s + "#" + rev;
int n = combined.size();
vector<int> lps(n, 0);
int len = 0, i = 1;
while (i < n) {
if (combined[i] == combined[len]) {
len++;
lps[i] = len;
i++;
} else {
if (len != 0) {
len = lps[len - 1];
} else {
lps[i] = 0;
i++;
}
}
}
int palinPrefixLen = lps[n - 1];
string suffix = s.substr(palinPrefixLen);
reverse(suffix.begin(), suffix.end());
return suffix + s;
}
};
int main() {
Solution sol;
cout << sol.shortestPalindrome("aacecaaa") << endl; // "aaacecaaa"
cout << sol.shortestPalindrome("abcd") << endl; // "dcbabcd"
cout << sol.shortestPalindrome("aba") << endl; // "aba"
cout << sol.shortestPalindrome("") << endl; // ""
return 0;
}
How it works: By constructing s + "#" + reverse(s), the LPS at the end gives us the length of the longest prefix of s that is also a suffix of reverse(s) — which is exactly the longest palindromic prefix of s. The # separator ensures we don’t get matches that span across the boundary.
Complexity: O(n) time, O(n) space.
41.4.4 Implement strStr() (LeetCode 28)
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Solution {
public:
int strStr(string haystack, string needle) {
if (needle.empty()) return 0;
int n = haystack.size(), m = needle.size();
if (m > n) return -1;
// Build LPS
vector<int> lps(m, 0);
int len = 0, idx = 1;
while (idx < m) {
if (needle[idx] == needle[len]) {
len++;
lps[idx] = len;
idx++;
} else {
if (len != 0) {
len = lps[len - 1];
} else {
lps[idx] = 0;
idx++;
}
}
}
// KMP search
int i = 0, j = 0;
while (i < n) {
if (haystack[i] == needle[j]) {
i++; j++;
}
if (j == m) {
return i - j;
}
if (i < n && haystack[i] != needle[j]) {
if (j != 0) j = lps[j - 1];
else i++;
}
}
return -1;
}
};
int main() {
Solution sol;
cout << sol.strStr("sadbutsad", "sad") << endl; // 0
cout << sol.strStr("leetcode", "leeto") << endl; // -1
cout << sol.strStr("hello", "ll") << endl; // 2
return 0;
}
Complexity: O(n + m) time, O(m) space.
Interview Tips
- Know the LPS construction cold. Interviewers often ask to implement KMP from scratch. The LPS construction is the tricky part — practice it until it’s muscle memory.
- Understand why KMP is O(n+m). The
ipointer never decreases (text pointer only moves forward). Thejpointer (pattern pointer) can decrease, but the total number of decreases is bounded by the total number of increases, which is ≤ n. - KMP vs other string algorithms. KMP is best for single-pattern matching. For multiple patterns, consider Aho-Corasick. For suffix-based queries, consider suffix arrays or suffix trees.
- The
#separator trick in Shortest Palindrome is a common pattern — use a character that doesn’t appear in the input to prevent cross-boundary matches.
Common Mistakes
- Off-by-one in LPS.
lps[i]is the length of the longest proper prefix-suffix ofpattern[0..i], not an index. Don’t confuse it with the position. - Forgetting to handle j==m before the mismatch check. In the search loop, check
j == mbefore checking for mismatches, otherwise you’ll miss matches at the very end. - Not using the
#separator in Shortest Palindrome. Without it, the LPS might find a match that’s not a true palindromic prefix. - Confusing LPS with Z-array. They’re related but different. LPS[i] = longest proper prefix of
p[0..i]that’s also a suffix. Z[i] = longest prefix match starting at position i.
Practice Problems
- Repeated String Match (LeetCode 686) — Find the minimum number of times to repeat
aso thatbis a substring. Hint: Repeatauntil its length ≥b’s length +a’s length, then use KMP. - Longest Happy Prefix (LeetCode 1392) — Find the longest prefix which is also a suffix. Hint: This is exactly
lps[n-1]. - Period of a String — Find the smallest period. Hint: If
lps[n-1] > 0andn % (n - lps[n-1]) == 0, the period isn - lps[n-1]. - Count and Say (LeetCode 38) — While not directly KMP, understanding pattern matching helps.
- Rotate String (LeetCode 796) — Check if one string is a rotation of another. Hint: Check if
sis a substring ofgoal + goal.
See Also
- Chapter 42: Z Algorithm — An alternative to KMP for pattern matching; Z-array computes the same information as the failure function but from a different perspective.
- Chapter 44: Suffix Array — For multiple pattern searches and substring queries; suffix arrays generalize single-pattern matching.
- Chapter 45: Suffix Automaton — A compressed automaton of all substrings; enables O(n) construction and O(m) pattern matching.
- Chapter 46: Aho-Corasick — Multi-pattern matching; extends KMP’s failure function to a trie of patterns.
- Chapter 40: Rolling Hash — Hash-based pattern matching as an alternative to KMP; simpler but probabilistic.
- Chapter 16: Trie — Aho-Corasick is built on tries; understanding trie structure is prerequisite.