Pattern #35
KMP / String Matching
AdvancedLinear-time pattern matching via failure function; rolling hash.
When to use
Use when searching for a pattern in a text in O(n + m). KMP for exact matching with a failure (LPS) array; rolling hash (Rabin-Karp) for multiple patterns or sliding hash.
Recognition cues
- Find pattern in text in linear time
- Repeated substring / LPS array
- Rolling hash over a sliding window
- Match multiple patterns
Common pitfalls
- Building the LPS array incorrectly (off-by-one)
- Hash collisions in Rabin-Karp (use double hash or verify)
- Modular arithmetic overflow in rolling hash
90-second recognition drill
Which pattern fits best?
- Find pattern in text in linear time
- Repeated substring / LPS array
- Rolling hash over a sliding window
Interactive
Mental model
A full worked walkthrough of the invariant. Pause, scrub the dots, or use ← →. Aim to narrate each step yourself.
pattern = ABAB
Find pattern ABAB in text. Naive matching restarts on mismatch; KMP reuses the prefix.
How to think about it
Naive matching is O(n*m): for every position, compare the pattern and backtrack on mismatch. KMP eliminates the backtrack with a longest-prefix-suffix (LPS) array: lps[i] is the longest proper prefix of the pattern that is also a suffix of pattern[0..i]. On a mismatch at position j, instead of restarting, jump j to lps[j-1] - you already matched that many characters. Build the LPS array once (O(m)), then scan the text once (O(n)).
Rabin-Karp takes a different route: hash the pattern, then roll a hash across the text. A rolling hash updates in O(1) (subtract the outgoing char, multiply by base, add the incoming char, mod prime). When the hash matches the pattern’s, verify character-by-character (to rule out collisions). It shines when matching multiple patterns (hash set) or when the text is a stream.
Template shapes
| Shape | Core move | Example |
|---|---|---|
| KMP exact match | Build LPS; scan text with jumps | LC 28 |
| Shortest palindrome | LPS of pattern + reverse(pattern) | LC 214 |
| Longest happy prefix | LPS of the string itself | LC 1392 |
| Rabin-Karp rolling hash | Hash pattern; roll hash across text | LC 3036 |
Complexity baseline
KMP: O(n + m) time, O(m) space (LPS). Rabin-Karp: O(n + m) average, O(n*m) worst case (many collisions); use a double hash to make collisions negligible. Rolling hash update: O(1).
From template to problem
- Is it exact single-pattern matching? → KMP. Multiple patterns or sliding? → Rabin-Karp.
- Build the LPS array carefully, the
lps[i]is the prefix length beforei, not ati. - For Rabin-Karp, pick a base > alphabet size and a large prime; take mod at every step.
- Always verify a hash match with a character check before returning.
Template
Same skeleton in TypeScript, Python, and C#. Adapt the invariant; keep the structure.
/** KMP template: build LPS array and find pattern in text. */
export function buildLps(pattern: string): number[] {
const lps = new Array<number>(pattern.length).fill(0);
let len = 0;
for (let i = 1; i < pattern.length; i++) {
while (len > 0 && pattern[i] !== pattern[len]) len = lps[len - 1]!;
if (pattern[i] === pattern[len]) len++;
lps[i] = len;
}
return lps;
}
export function kmpSearch(text: string, pattern: string): number {
if (pattern.length === 0) return 0;
const lps = buildLps(pattern);
let j = 0;
for (let i = 0; i < text.length; i++) {
while (j > 0 && text[i] !== pattern[j]) j = lps[j - 1]!;
if (text[i] === pattern[j]) j++;
if (j === pattern.length) return i - j + 1;
}
return -1;
}/** KMP template: build LPS array and find pattern in text. */
export function buildLps(pattern: string): number[] {
const lps = new Array<number>(pattern.length).fill(0);
let len = 0;
for (let i = 1; i < pattern.length; i++) {
while (len > 0 && pattern[i] !== pattern[len]) len = lps[len - 1]!;
if (pattern[i] === pattern[len]) len++;
lps[i] = len;
}
return lps;
}
export function kmpSearch(text: string, pattern: string): number {
if (pattern.length === 0) return 0;
const lps = buildLps(pattern);
let j = 0;
for (let i = 0; i < text.length; i++) {
while (j > 0 && text[i] !== pattern[j]) j = lps[j - 1]!;
if (text[i] === pattern[j]) j++;
if (j === pattern.length) return i - j + 1;
}
return -1;
}- 1#28 Find the Index of the First Occurrence in a StringGuideeasy
- 2#1392 Longest Happy PrefixGuidehard
- 3#1408 String Matching in an ArrayGuideeasy
- 4#214 Shortest PalindromeGuidehard
- 5#459 Repeated Substring PatternGuideeasy
- 6#3036 Number of Subarrays That Match a Pattern IIGuidehard