Algorithm tutorials
KMP and the Prefix Function
The prefix function records, for every prefix, its longest border (a proper prefix that is also a suffix). With it, KMP finds all occurrences of a pattern in O(n + m) and answers period, repetition and palindrome-prefix questions.
Intuition
String matching: find every position where a pattern p (length m) occurs in a text t (length n). The brute-force matcher tries every start and compares up to m characters, O(n * m) in the worst case (think t = "aaaa...ab", p = "aa...ab"). Its waste: after a mismatch it forgets everything it just matched.
KMP keeps that knowledge. Suppose j characters of the pattern matched and the next one fails. The matched part p[:j] is known text, so the next possible alignment is the longest proper prefix of p[:j] that is also its suffix, called its longest border. Slide the pattern so that this border lines up and continue; the text pointer never moves back.
The prefix function stores those borders: pi[i] = length of the longest proper prefix of s[:i + 1] that is also a suffix of it. For s = "abacaba", pi = [0, 0, 1, 0, 1, 2, 3] (the whole string ends with "aba", which is also how it starts).
Computing pi reuses itself: to extend the border at position i, try the current border j = pi[i - 1]; if s[i] != s[j], fall back to the next shorter border pi[j - 1], and so on. j grows by at most 1 per step and every fallback shrinks it, so the total work is linear.
When to use it in interviews / OAs
- Find the first or all occurrences of a needle (implement
strStr), counting overlapping matches too. - "Longest prefix that is also a suffix" (longest happy prefix): it is
pi[-1]. - Periodicity: smallest period, "is the string some block repeated
k >= 2times?". - "Shortest palindrome by adding characters in front": longest palindromic prefix via
s + "#" + reverse(s). - Matching in a stream: KMP consumes the text one character at a time and never rereads it.
If the interviewer only needs "does it occur", a rolling hash is shorter to write; KMP wins when you need guarantees (no collisions) or the border structure itself.
Template
Brute force baseline
def brute_search(text: str, pattern: str) -> list[int]:
n, m = len(text), len(pattern)
return [i for i in range(n - m + 1) if text[i:i + m] == pattern] # O(n * m)
Prefix function and KMP search
def prefix_function(s: str) -> list[int]:
pi = [0] * len(s)
for i in range(1, len(s)):
j = pi[i - 1] # longest border of s[:i]
while j > 0 and s[i] != s[j]:
j = pi[j - 1] # fall back to the next shorter border
if s[i] == s[j]:
j += 1
pi[i] = j
return pi
def kmp_search(text: str, pattern: str) -> list[int]:
if not pattern:
return list(range(len(text) + 1))
pi = prefix_function(pattern)
res, j = [], 0 # j = number of pattern characters matched so far
for i, ch in enumerate(text):
while j > 0 and ch != pattern[j]:
j = pi[j - 1]
if ch == pattern[j]:
j += 1
if j == len(pattern):
res.append(i - j + 1)
j = pi[j - 1] # continue: occurrences may overlap
return res
An equivalent one-liner idea: compute prefix_function(pattern + "#" + text) with a separator that occurs in neither string; every position where the value equals m ends an occurrence.
Borders, periods and palindromic prefixes
If s has a border of length b, then len(s) - b is a period (s[i] == s[i + p] for every valid i). The longest border gives the smallest period.
def smallest_period(s: str) -> int:
return len(s) - prefix_function(s)[-1] if s else 0
def is_repeated(s: str) -> bool:
# s == block * k for some k >= 2
p = smallest_period(s)
return p < len(s) and len(s) % p == 0
def longest_happy_prefix(s: str) -> str:
return s[:prefix_function(s)[-1]] if s else ""
def shortest_palindrome(s: str) -> str:
# longest palindromic prefix of s = longest border of s + "#" + reverse(s)
rev = s[::-1]
k = prefix_function(s + "#" + rev)[-1] # "#" must not occur in s
return rev[:len(s) - k] + s
Complexity
prefix_function is O(n) time and space; KMP search is O(n + m) time and O(m) extra space (only the pattern's pi is stored). The brute force is O(n * m) in the worst case.
Common pitfalls
- Off-by-one in
pi.pi[i]describes the prefix ending at indexi(lengthi + 1); afterjmatched characters the fallback ispi[j - 1], notpi[j]. - Not resetting after a full match. Without
j = pi[j - 1], the next comparison readspattern[m]and crashes, or you miss overlapping occurrences. - Separator collisions. In
pattern + "#" + text, the separator must not appear in either string, or borders can leak across it. - Empty pattern. Decide what it should return (Python's
findreturns 0) and handle it before indexingpattern[0]. - Period vs. repetition. The smallest period
palways exists, but the string is a full repetition only ifpdividesnandp < n("abcab"has period 3 but is not a repetition). - Confusing
piwith the Z-function.pi[i]is about the substring ending ati;z[i]is about the substring starting ati.
Interview variations
- Repeated substring pattern.
is_repeated(s); an alternative to thes in (s + s)[1:-1]trick, which is only linear if the substring search is. - Rotation check.
bis a rotation ofaifflen(a) == len(b)andboccurs ina + a(run KMP). - All borders. Follow
pi[n - 1],pi[pi[n - 1] - 1], ... down to 0; each one is a prefix that is also a suffix. - Count occurrences of every prefix of
sinsides: addpivalues into buckets and push counts from longer to shorter borders,O(n)in total. - Shortest palindrome / longest palindromic prefix. The
s + "#" + reverse(s)construction above. - KMP automaton. Precomputing transitions for every (state, letter) pair supports DP over strings that must avoid a pattern; usually beyond interview scope.