Overview
String matching finds occurrences of a pattern of length m inside a text of length n. The naive approach checks every starting position and costs O(n x m) in the worst case. Linear-time algorithms avoid re-examining characters by preprocessing the pattern.
KMP (Knuth-Morris-Pratt) builds an LPS array: for each prefix of the pattern, the length of its longest proper prefix that is also a suffix. On a mismatch, it jumps the pattern forward using that array instead of restarting, giving O(n + m). The Z-algorithm and rolling hashes (Rabin-Karp) are the other common linear approaches.
If you are searching for 'ABABC' and have matched 'ABAB' before failing, you already know the last 'AB' you read is also the start of the pattern. KMP slides the pattern so that 'AB' lines up and continues from there, instead of starting over one character later.
When to use it
- Find the first or all occurrences of a pattern in text.
- Repeated or periodic structure in a string (repeated substring pattern).
- Shortest palindrome and prefix-suffix problems.
- Large inputs where O(n x m) is too slow.
Problem patterns it solves
Recognize it when: index of first occurrence, count occurrences.
- 28. Find the Index of the First Occurrence in a String
- 1392. Longest Happy Prefix
Recognize it when: string made of a repeated block.
- 459. Repeated Substring Pattern
- 686. Repeated String Match
Recognize it when: longest prefix that is also a suffix; shortest palindrome.
- 1392. Longest Happy Prefix
- 214. Shortest Palindrome
Recognize it when: is one string a rotation of another: search in s + s.
- 796. Rotate String
Recognize it when: many patterns at once (Aho-Corasick or a trie).
- 1032. Stream of Characters
- 212. Word Search II
Where it is used in real software
Find in file, grep, and search in browsers use efficient string search algorithms such as Boyer-Moore variants.
Network security tools like Snort match thousands of attack signatures in traffic using Aho-Corasick.
Searching for gene sequences in DNA uses linear-time matching and suffix structures.
Detect known phrases or templates in documents and emails.
Key terms
- LPS array
- lps[i] = length of the longest proper prefix of pattern[0..i] that is also a suffix.
- Proper prefix
- A prefix that is not the whole string.
- Z-array
- z[i] = length of the longest substring starting at i that matches a prefix.
- Boyer-Moore
- Compares from the pattern's end and skips ahead using bad-character rules; fast in practice.
KMP in two phases
- 1Build LPS
Walk the pattern with len = current matched prefix length. On match, len++ and lps[i] = len. On mismatch, fall back len = lps[len - 1] (or set 0).
- 2Search
Walk the text with j = number of pattern characters matched.
- 3Match
If text[i] === pattern[j], advance both; if j === m, a match ends at i.
- 4Mismatch
If j > 0, set j = lps[j - 1] and retry the same text character. Otherwise advance i.
- 5Never move the text pointer backward
That is why the total is O(n + m).
LPS array for pattern "ABABCABAB"
lps[i] = longest proper prefix of pattern[0..i] that is also its suffix
| i | char | Prefix | Longest prefix = suffix | lps[i] |
|---|---|---|---|---|
| 0 | A | A | - | 0 |
| 1 | B | AB | - | 0 |
| 2 | A | ABA | A | 1 |
| 3 | B | ABAB | AB | 2 |
| 4 | C | ABABC | - | 0 |
| 5 | A | ABABCA | A | 1 |
| 6 | B | ABABCAB | AB | 2 |
| 7 | A | ABABCABA | ABA | 3 |
| 8 | B | ABABCABAB | ABAB | 4 |
NOWi: 0 | char: A | Prefix: A | Longest prefix = suffix: - | lps[i]: 0
After matching the whole pattern, KMP continues as if 4 characters (ABAB) are already matched, so it finds overlapping occurrences without rescanning the text. On a mismatch after matching ABABCABA (8 characters), it resumes with lps[7] = 3 characters matched.
Implementation
function buildLPS(pattern) { const lps = new Array(pattern.length).fill(0); let len = 0; for (let i = 1; i < pattern.length; ) { if (pattern[i] === pattern[len]) { lps[i++] = ++len; } else if (len > 0) { len = lps[len - 1]; // try a shorter border } else { lps[i++] = 0; } } return lps;} function kmpSearch(text, pattern) { if (!pattern) return [0]; const lps = buildLPS(pattern); const matches = []; 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) { matches.push(i - j + 1); j = lps[j - 1]; // continue for overlapping matches } } return matches;} // 459. Repeated Substring Pattern using LPSfunction repeatedSubstringPattern(s) { const lps = buildLPS(s); const border = lps[s.length - 1]; const period = s.length - border; return border > 0 && s.length % period === 0;} // 214. Shortest Palindrome: longest palindromic prefix via LPS of s + '#' + reverse(s)function shortestPalindrome(s) { const rev = [...s].reverse().join(""); const lps = buildLPS(s + "#" + rev); return rev.slice(0, s.length - lps.at(-1)) + s;}Complexity and performance
Worst case like text aaaa...ab.
O(m) extra space for LPS.
Same bounds, different preprocessing.
Implementations vary; worst case may be O(n x m).
Trade-offs
In interviews you usually may use indexOf; implement KMP when asked, or when the LPS array itself is useful (periodicity, borders).
KMP is deterministic; Rabin-Karp is simpler to extend to multiple patterns and 2D, but relies on hashing and has collision risk.
Variants and related techniques
A trie of many patterns with failure links (KMP on a trie); finds all patterns in one pass.
Answers many substring queries on one text after O(n log n) or O(n) preprocessing.
Finds the longest palindromic substring in O(n) using a similar reuse-of-work idea.
Common mistakes
- Advancing i after a fallback in the LPS build.
Fix: When len = lps[len - 1], stay on the same i and compare again.
- Missing overlapping matches.
Fix: After a full match set j = lps[j - 1], not 0.
- Separator collisions in combined strings.
Fix: Use a character that cannot appear in the input (#, $).
Interview questions
What does the LPS array represent?
For each position, the length of the longest proper prefix of the pattern that is also a suffix of the pattern up to that position. It tells KMP how much of the pattern is still matched after a mismatch.
Why is KMP O(n + m)?
The text pointer never moves backward. Each fallback reduces j, and j can only increase once per text character, so total fallbacks are bounded by n.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| 28. Find the Index of the First Occurrence | Easy | Implement KMP. |
| 796. Rotate String | Easy | Search in s + s. |
| 459. Repeated Substring Pattern | Easy | Period from LPS. |
| 1392. Longest Happy Prefix | Hard | Final LPS value. |
| 214. Shortest Palindrome | Hard | LPS of combined string. |