Courses › Coding Interviews › Patterns
String Matching
Rolling hashes (Rabin–Karp) and the prefix function (KMP), and when to use each.
"Find a pattern in a text" can be solved with a naive O(n × m) scan, and that's often acceptable in an interview. When the interviewer asks for better, or when a problem needs to compare many substrings, two techniques cover nearly every case: rolling hashes (Rabin–Karp) and the prefix function (KMP).
Naive matching, and when it's enough
def find(text, pat):
for i in range(len(text) - len(pat) + 1):
if text[i:i + len(pat)] == pat:
return i
return -1This is O(n × m) in the worst case ("aaaa…ab" searched for "aaab"). In Python, text.find(pat) is heavily optimized, so mention it. But be ready to implement one of the linear-time algorithms if asked.
Rolling hash (Rabin–Karp)
Treat each window of length m as a number in base B, modulo a large prime. Sliding the window removes the leading character and adds the trailing one in O(1). Equal hashes are candidates. Compare the actual strings to rule out collisions.
def rabin_karp(text, pat, base=257, mod=(1 << 61) - 1):
n, m = len(text), len(pat)
if m > n:
return -1
high = pow(base, m - 1, mod) # weight of the leading character
hp = ht = 0
for i in range(m):
hp = (hp * base + ord(pat[i])) % mod
ht = (ht * base + ord(text[i])) % mod
for i in range(n - m + 1):
if hp == ht and text[i:i + m] == pat: # verify to rule out a collision
return i
if i + m < n:
ht = ((ht - ord(text[i]) * high) * base + ord(text[i + m])) % mod
return -1Rolling hashes really shine when you need to compare many substrings of the same length:
- Repeated DNA sequences: hash every window of length 10, and look for repeats in a set.
- Longest duplicate substring: binary search on the length, then check each length with rolling hashes. O(n log n).
- Distinct substrings and palindrome checks with prefix hashes, giving O(1) comparisons of any two substrings.
[!WARNING] Collisions Different strings can share a hash. Either verify each match by comparing the strings, or use a very large modulus (or two independent hashes) and say that a collision is extremely unlikely. Don't ignore collisions silently. Interviewers will ask about them.
KMP and the prefix function
The prefix function pi[i] is the length of the longest proper prefix of s[:i+1] that is also a suffix of it. When a match fails, KMP uses pi to skip ahead without re-scanning the text, so the whole search is O(n + m).
def prefix_function(s):
pi = [0] * len(s)
k = 0
for i in range(1, len(s)):
while k and s[i] != s[k]:
k = pi[k - 1] # fall back to the next shorter border
if s[i] == s[k]:
k += 1
pi[i] = k
return pi
def kmp_search(text, pat):
if not pat:
return 0
pi = prefix_function(pat + "\0" + text) # separator must not appear in either string
m = len(pat)
for i in range(m + 1, len(pi)):
if pi[i] == m:
return i - 2 * m
return -1The prefix function also solves several problems directly:
Which one to use
[!INTERVIEW] Lead with the simple solution Start with the naive scan, and state its worst case. Then offer the linear-time improvement. Writing KMP from memory without explaining why it works rarely impresses. Explaining the "fall back to the next shorter border" idea does.
[!TIP] Practice Implement strStr() · Repeated DNA Sequences · Rotate String · Longest Palindromic Substring · Substring with Concatenation of All Words