Lesson 38 / 42
String Matching
Finding a pattern inside text efficiently — KMP's failure function and a glance at Rabin-Karp hashing.
Why not brute force?
Naively sliding the pattern across the text and comparing is O(n*m). KMP (Knuth-Morris-Pratt) avoids re-checking characters it already matched by precomputing, for the pattern, how far to fall back on a mismatch — giving O(n + m).
KMP failure function
lps[i] stores the length of the longest proper prefix of pattern[0..i] that is also a suffix — this tells KMP how far to shift on a mismatch instead of restarting.
def build_lps(pattern):
lps = [0] * len(pattern)
length = 0
i = 1
while i < len(pattern):
if pattern[i] == pattern[length]:
length += 1
lps[i] = length
i += 1
elif length:
length = lps[length - 1]
else:
lps[i] = 0
i += 1
return lps
def kmp_search(text, pattern):
lps = build_lps(pattern)
matches, i, j = [], 0, 0
while i < len(text):
if text[i] == pattern[j]:
i, j = i + 1, j + 1
if j == len(pattern):
matches.append(i - j)
j = lps[j - 1]
elif j:
j = lps[j - 1]
else:
i += 1
return matches
Output:
kmp_search('ababcabab', 'abab') # [0, 5]Rabin-Karp at a glance
Rabin-Karp hashes the pattern and every window of the text, comparing hashes first (O(1) via a rolling hash) and only verifying character-by-character on a hash match. Great for searching many patterns at once.
Quick check
Test KMP's complexity.
Quick check: KMP's overall time complexity for searching pattern of length m in text of length n is:
- O(n*m)
- O(n + m)
- O(n log m)
- O(m^2)
Answer
O(n + m) — The LPS array is built in O(m), and the search itself is O(n) since the text pointer never backtracks.