Sequences & Strings/String Matching: KMP & the Z-Function

Lesson 5.63,252 words

String Matching: KMP & the Z-Function

Two linear-time matchers that beat Rabin–Karp's expected bound with a worst-case guarantee and no randomness. KMP precomputes a failure function π\pi so a mismatch slides the pattern by qπ[q1]q-\pi[q-1] and the text pointer never backs up, for O(n+m)O(n+m).

╌╌╌╌

This builds on String Matching: Naive & Rabin–Karp. There, Rabin–Karp removed the per-alignment cost — a length- comparison became a length- hash update — but its guarantee was only expected-time, and it still slid the window one position at a time with no memory of what a partial match had revealed. The two algorithms here attack the re-reading instead. Both precompute the pattern's internal self-overlap, so that after a mismatch the scan resumes exactly where the pattern's structure permits, never re-examining a text character it already knows. The payoff is a worst-case bound with no randomness at all.

Knuth–Morris–Pratt: never re-read the text

KMP attacks the redundancy directly. Suppose we are matching against and have just matched characters, , when mismatches . The naive scan would slide by one and recompare from the start. But the matched text equals , which we know completely — so we can compute, in advance and from the pattern alone, how far to slide so the next comparison resumes correctly without ever moving the text pointer backward.

The information we need is the prefix function (or failure function):

For we have : after matching the prefix ABABA (length , index ), its longest border is ABA of length . So on a mismatch having matched characters, instead of restarting we keep the already-matched border ABA and resume comparing at . The pattern effectively slides right by positions, and the text pointer does not move.

A KMP shift. After matching ABABA, P fails on C vs the text (highlighted). The longest border ABA realigns, sliding P right by with no rescanning of the text.

Computing uses the very same self-matching idea, run on against itself. Maintain the length of the current longest border; to extend to index , follow failure links downward until or falls to .

Algorithm:Prefix-Function(P)\textsc{Prefix-Function}(P) — compute the failure array π\pi in O(m)O(m)
  1. 1
    π[0]0; k0\pi[0] \gets 0;\ k \gets 0
  2. 2
    for i1i \gets 1 to m1m - 1 do
  3. 3
    while k>0k > 0 and P[k]P[i]P[k] \ne P[i] do
  4. 4
    kπ[k1]k \gets \pi[k - 1]
    fall back
  5. 5
    if P[k]=P[i]P[k] = P[i] then
  6. 6
    kk+1k \gets k + 1
    extend border
  7. 7
    π[i]k\pi[i] \gets k
  8. 8
    return π\pi
Algorithm:KMP-Match(T,P)\textsc{KMP-Match}(T, P) — scan once, never moving the text pointer back
  1. 1
    πPrefix-Function(P)\pi \gets \textsc{Prefix-Function}(P)
  2. 2
    q0q \gets 0
    chars matched so far
  3. 3
    for i0i \gets 0 to n1n - 1 do
  4. 4
    while q>0q > 0 and P[q]T[i]P[q] \ne T[i] do
  5. 5
    qπ[q1]q \gets \pi[q - 1]
    mismatch: fall back
  6. 6
    if P[q]=T[i]P[q] = T[i] then
  7. 7
    qq+1q \gets q + 1
  8. 8
    if q=mq = m then
  9. 9
    report occurrence at shift im+1i - m + 1
  10. 10
    qπ[q1]q \gets \pi[q - 1]
    allow overlapping matches

The key is that is a potential that pays for the fallback: each unit of slide was funded by an earlier successful character match.1 No text character is ever examined more than a constant number of times, the central property that the naive scan lacks.

kmp.pypython
def prefix_function(pattern: str) -> list[int]:
  """
    The failure array pi, where `pi[i]` is the length of the longest proper\n
    prefix of `pattern[0 .. i]` that is also a suffix of it.\n
  """
  length: int = len(pattern)
  border_lengths: list[int] = [0 for _ in range(length)]
  border: int = 0
  for index in range(1, length):

    # follow failure links down until the next char matches or we hit 0.
    while border > 0 and pattern[border] != pattern[index]:
      border = border_lengths[border - 1]
    if pattern[border] == pattern[index]:
      border += 1

    # record the border length for this prefix.
    border_lengths[index] = border
  return border_lengths

def kmp_match(text: str, pattern: str) -> list[int]:
  """
    Every shift `s` where `pattern` occurs in `text`, in increasing order.\n
    An empty pattern matches at every position `0 .. len(text)`.\n
  """
  text_length: int = len(text)
  pattern_length: int = len(pattern)
  if pattern_length == 0:
    return list(range(text_length + 1))

  borders: list[int] = prefix_function(pattern)
  shifts: list[int] = []
  matched: int = 0
  for index in range(text_length):

    # mismatch: slide the pattern via the longest border, text stays put.
    while matched > 0 and pattern[matched] != text[index]:
      matched = borders[matched - 1]
    if pattern[matched] == text[index]:
      matched += 1

    # full match: record the shift, then fall back to find overlaps.
    if matched == pattern_length:
      shifts.append(index - pattern_length + 1)
      matched = borders[matched - 1]
  return shifts

Building the failure function by hand

Run on and watch , the length of the current longest border. Each step tries to extend the border of the previous prefix by one character; when that fails, falls back through shorter borders until one extends or none is left.

  • : ; compare with — no match, and is already , so .
  • : compare with — match, so and (border A).
  • : compare with — match, , (border AB).
  • : compare with — match, , (border ABA).
  • : compare with — mismatch. Fall back: ; compare with C — mismatch again. Fall back: ; compare with C — still no. , so . Two fallbacks, one failed extension: the cost of this step equals the border length that steps built up.
  • : compare with — match, , .

The full table, read left to right: is the length of the longest border of the prefix ending at column .

The prefix function for . Top row: the pattern. Bottom row: , the length of the longest proper prefix of that is also a suffix. E.g. because ABABA has border ABA.

Step shows the fallback chain in action, and the geometry is worth drawing. Having matched the border ABA and failed to extend it with B against C, the next candidate is not border of length — it is , the longest border of the border. The figure shows all three candidates being tried against column in decreasing order:

Computing for : the fallback chain. Each row aligns a candidate border (blue cells) under the end of the prefix and tests its next character (red) against . Candidates are , then , then ; all fail, so

Why is the right place to fall back to — why not some length in between? Because borders nest.

The construction's linearity is the same two-line count as the matcher's. Across the whole run, increases only in the if (at most once per , so at most times) and strictly decreases with every while iteration (since ). It starts at and never goes negative, so

and the total work is at most . Step above spent two fallbacks in one step precisely because steps had deposited three increments; the budget balances globally even though a single step can be expensive.

The matcher on a real text

Match against (), carrying from above. The variable counts matched characters:

  • : A, B, A, B, A all match, climbs .
  • : vs — mismatch. Fall back : the border ABA of the matched ABABA is still a live partial match, ending where we stand. Now matches , so . The text pointer never moved; was never re-read.
  • : matches, .
  • : matches, .
  • : matches, — report an occurrence at shift (indeed ), then reset so an overlapping occurrence starting with the final A stays catchable.
  • , : and match, reaches as the text runs out.

Eleven text characters, one fallback, one report. Only was compared against the pattern twice — once failing against C, once matching B — and no position was touched a third time, comfortably within the comparison budget the potential argument promises.

KMP scanning for . Below each text position: the matched count after that step. At (red) the mismatch drops from to and re-matches to ; at (blue) reaches , reporting the occurrence at shift

The Z-function: longest prefix-match at every position

The Z-function repackages the same self-similarity into a single array, and is often easier to implement bug-free.

The linear-time computation keeps a half-open window , the Z-box: the rightmost interval known to equal a prefix of (so ). For a new index :

  • if , position lies inside a known prefix-match, so its mirror gives a free lower bound ;
  • then extend the match character-by-character past if possible, and if the match runs beyond , slide the Z-box to the new .
Z-box copy on at . The box already matches a prefix, so the mirror gives a free bound ; here caps the copy
Algorithm:Z-Function(S)\textsc{Z-Function}(S) — longest prefix-match at each position in O()O(\ell)
  1. 1
    Z[0]; l0; r0Z[0] \gets \ell;\ l \gets 0;\ r \gets 0
  2. 2
    for i1i \gets 1 to 1\ell - 1 do
  3. 3
    if i<ri < r then
  4. 4
    Z[i]min(ri, Z[il])Z[i] \gets \min(r - i,\ Z[i - l])
    copy from mirror
  5. 5
    while i+Z[i]<i + Z[i] < \ell and S[Z[i]]=S[i+Z[i]]S[Z[i]] = S[i + Z[i]] do
  6. 6
    Z[i]Z[i]+1Z[i] \gets Z[i] + 1
    extend past the box
  7. 7
    if i+Z[i]>ri + Z[i] > r then
  8. 8
    li; ri+Z[i]l \gets i;\ r \gets i + Z[i]
    advance Z-box
  9. 9
    return ZZ

The case split hides the whole efficiency argument, so make it explicit. When , the box guarantees : everything from to the box's edge is a verbatim copy of the string starting at the mirror position . Two things can happen:

  • Mirror case, . The mirror's prefix-match ended strictly inside the copied region, mismatch included: , and the corresponding character is still inside the box, so it equals the mirror's and mismatches too. Hence exactly; the while test fails on its first probe and the box does not move. Cost: .
  • Extension case, . The mirror matched at least to the box's edge, so all of matches the prefix for free — but the box says nothing about onward. Start comparing at position ; every match pushes past everything the box ever certified, and afterward the box advances to .

(When the box is useless and we are in the extension case with a free match of length .)

A full trace

Run it on (), chosen so both cases fire:

  • : box empty; compare fresh: , then . , box .
  • , : fresh compares fail at once (, ): .
  • : fresh: aab matches, then . , box .
  • : inside the box; mirror has . Mirror case: , one failed probe, box unchanged. (Indeed , — the mirror's mismatch, replayed.)
  • : mirror : mirror case again, .
  • : , so outside the box: fresh compare, , .
  • : fresh: aa matches, . , box .
  • : inside the box; mirror . Extension case: the first character is free, and comparing at gives — a hit past the old box — then . , box advances to .
  • : mirror : extension case; the probe at fails (... precisely ), so and the box stays.
  • : outside the box (): fresh compare fails, .

The result is , computed with eight hits and ten misses — inside the budget with room to spare.

The two Z-box cases on . Top (, box ): the mirror's match ends inside the box, so is copied and no probe succeeds. Bottom (, box ): the mirror reaches the box edge, so comparison resumes at ; the a matches (blue), the z fails (red), and the box advances to

To match in , run the Z-function on the concatenation , where # is a sentinel in neither nor . Wherever inside the portion, the prefix matches in full at that spot, so occurs in at shift . The sentinel prevents a match from straddling the boundary and caps . Total length is , so matching is .

z_function.pypython
def z_function(string: str) -> list[int]:
  """
    The Z-array of `string`. `Z[0]` is set to the string length by\n
    convention; for `i > 0`, `Z[i]` is the length of the longest prefix of\n
    `string` that also starts at position i.\n
  """
  length: int = len(string)
  if length == 0:
    return []

  # Z[0] is the whole string by convention.
  prefix_match: list[int] = [0 for _ in range(length)]
  prefix_match[0] = length

  # [box_left, box_right) is the rightmost known prefix-matching interval.
  box_left: int = 0
  box_right: int = 0

  for index in range(1, length):

    # inside the box: the mirror position gives a free lower bound.
    if index < box_right:
      prefix_match[index] = min(box_right - index, prefix_match[index - box_left])

    # extend the match character by character past the box.
    while (
      index + prefix_match[index] < length
      and string[prefix_match[index]] == string[index + prefix_match[index]]
    ):
      prefix_match[index] += 1

    # if the match reaches further right than the box, advance the box.
    if index + prefix_match[index] > box_right:
      box_left = index
      box_right = index + prefix_match[index]
  return prefix_match

def z_match(text: str, pattern: str, sentinel: str = "\x00") -> list[int]:
  """
    Every shift `s` where `pattern` occurs in `text`, in increasing order.\n
    Runs `z_function` on `pattern + sentinel + text`; the sentinel must not\n
    occur in either string so no match straddles the boundary.\n
  """
  pattern_length: int = len(pattern)
  if pattern_length == 0:
    return list(range(len(text) + 1))

  combined: str = pattern + sentinel + text
  prefix_match: list[int] = z_function(combined)

  # scan only the text portion; a full-length prefix match is an occurrence.
  return [
    index - pattern_length - 1
    for index in range(pattern_length + 1, len(combined))
    if prefix_match[index] >= pattern_length
  ]

What the tables give you for free

The self-overlap tables answer far more than does occur in . Two payoffs recur so often they are worth naming.

Periods and repetitions. A string of length has period if wherever both are defined — equivalently, is a border of . So the smallest period is , read straight off the last prefix-function entry. For (), the longest border has length (ABABA), so the smallest period is : the string is AB repeated, plus a trailing A. From the Z-side, is a full -fold repetition of a block of length exactly when and . This is the whole content of Longest Happy Prefix (report the longest border, ) and of is this string a repeated block?.

Prefix structure and palindromes. Because is the longest prefix-match starting at , a single Z-pass over a cleverly chosen concatenation solves a family of problems: prepend the reverse of with a sentinel to test which prefixes of are palindromes (the idea behind Shortest Palindrome), or run Z on to find every occurrence with match lengths attached, which the plain boolean matcher discards.

Reading the smallest period off . For the longest border is ABABA (length 5), so period : the block `AB' tiles with one character left over

Choosing a matcher

Across both string-matching lessons there are four correct algorithms; the choice is about constants, guarantees, and what else the problem asks for.

  • Naive wins more often than its worst case suggests. For or so, or for a one-off search in text without heavy repetition, the expected cost is with tiny constants and zero preprocessing. Reject it only when the input may be adversarial or repetitive.
  • Rabin–Karp is the only one that generalizes cheaply to many patterns at once and to numeric or 2-D fingerprinting; its guarantee is expected-time only, and soundness depends on the verify step.
  • KMP gives the worst-case bound with no randomness, and its scan is online: it reads strictly left to right, one character at a time, keeping only and the table between characters. That makes it suitable for streams too large to store and the conceptual basis for matching automata (Aho–Corasick is KMP's failure idea on a trie of many patterns).
  • Z matches KMP's guarantee when the whole input can be concatenated up front, and the array it produces is often the main value: periods, borders, and prefix structure read straight off it. Many string exercises reduce to one Z-pass plus a scan.

To summarize both lessons: naive re-derives information it already had; hashing compresses a window to a comparable summary; and precompute the pattern's self-overlap so no information is ever re-derived. The next lesson pushes the same idea further, preprocessing the text itself into suffix arrays so that many queries against one text each cost near nothing.

The string-matching lineage and periodicity theory

KMP is named for the 1977 paper of Knuth, Morris, and Pratt (Fast Pattern Matching in Strings, SIAM J. Comput.), the result that first broke the barrier for exact matching in the worst case; Morris and Pratt had the linear matcher and Knuth supplied the analysis connecting it to the theory of periods. The Boyer–Moore algorithm (Boyer & Moore, A Fast String Searching Algorithm, CACM 1977), published the same year, attacks the problem from the opposite end — it matches the pattern right to left and uses a bad character rule to skip ahead by more than one position, so on typical text it examines sublinear in characters and is what grep and many memmem implementations actually use.

The prefix-function / period machinery is also the gateway to the deeper theory of string periodicity. The Fine–Wilf theorem (Fine & Wilf, 1965) states that if a string of length has two periods and with , then it also has period — the structural fact behind the nesting of border chains, and the reason KMP's fallback is well-defined. The same periodicity theory drives modern results such as constant-space and packed string matching, and the failure-link idea generalizes directly to the Aho–Corasick automaton for many patterns and to suffix automata for indexing a text.

The Z-function, by contrast, is folklore rather than a single named paper — it is the -algorithm of the competitive-programming and stringology literature (Gusfield's Algorithms on Strings, Trees, and Sequences, 1997, presents it as the fundamental preprocessing tool and derives KMP, Boyer–Moore, and more from it). Its appeal is pedagogical and practical in equal measure: one short, hard-to-get-wrong loop that exposes a string's entire prefix structure, from which periods, borders, and many matching problems fall out by a single scan.

Takeaways

  • KMP precomputes the prefix/failure function , so on a mismatch the pattern slides by and the text pointer never backs up: worst-case , justified by an amortised potential argument on .
  • The failure array is built by the same two-pointer self-matching, in ; its correctness rests on the fact that a string's borders nest, so the fallback chain enumerates every candidate in decreasing order.
  • The Z-function computes the longest prefix-match at every position in via the Z-box; running it on and finding matches in .
  • and are interconvertible — two encodings of a string's self-similarity — and both give periods for free: the smallest period is .
  • KMP/Z give worst-case guarantees with no randomness; Rabin–Karp trades that for simplicity and multi-pattern flexibility. KMP's online scan handles unbounded streams.

Footnotes

  1. CLRS, Ch. 32 — String Matching (§32.4): the prefix function and the amortised KMP analysis.
  2. Erickson, Ch. — String Matching: the Z-function / failure-function duality and the Z-box linear-time computation.
Practice

╌╌ END ╌╌