LeetCode The Hard Way
Strings

Knuth-Morris-Pratt Algorithm

Learn how KMP uses the longest proper prefix-suffix array to find a pattern in linear time.

Author@wkw

Overview

The Knuth-Morris-Pratt (KMP) algorithm finds a pattern inside a text in O(n+m)O(n + m) time, where nn is the length of the text and mm is the length of the pattern.

For example, in 28 - Find the Index of the First Occurrence in a String, the text is haystack\text{haystack} and the pattern is needle\text{needle}. We return the starting index of the first match, or −1-1 if no match exists.

A brute force solution tries every possible starting position in the text. Each attempt can compare up to mm characters, giving O(nm)O(nm) time in the worst case. This repeats a lot of work when the strings contain repeated characters, such as a long text of As and a pattern ending in B.

KMP preprocesses the pattern so that, after a mismatch, it can reuse characters that have already matched. The text pointer never moves backward.

Throughout this tutorial, indices are zero-based and slices such as pattern[:j]\text{pattern}[:j] include the start but exclude the end.

Intuition: Reuse the Matched Prefix

Suppose the text is ABABABACA and the pattern is ABABACA.

text:     A B A B A B A C A
pattern:  A B A B A C A
                    ^
                 mismatch

The first five characters, ABABA, match. At text index 55, however, we find B instead of the expected C.

The matched part ABABA has a suffix ABA that is also a prefix of the pattern. We already know that the last three matched characters can start another match, so we keep those three characters and compare the current text character with pattern index 33.

text:     A B A B A B A C A
pattern:      A B A B A C A
                    ^
              compare again

The remaining characters now match, giving a match starting at index 22.

The key question is: after a mismatch, how many matched characters can we keep? The LPS array answers this without rescanning the text.

The Longest Proper Prefix-Suffix Array

A prefix starts at the beginning of a string, and a suffix ends at its end. A proper prefix is shorter than the whole string. A prefix that is also a suffix is sometimes called a border.

lps[i]\text{lps}[i] is the length of the longest proper prefix of pattern[:i+1]\text{pattern}[:i + 1] that is also a suffix of that same substring. If none is nonempty, the value is 00.

The prefix must be proper; otherwise, the entire substring would always match itself. Prefixes and suffixes are allowed to overlap.

For pattern=ABABACA\text{pattern} = \texttt{ABABACA}:

Index iiCharacterSubstring pattern[:i+1]\text{pattern}[:i + 1]Longest proper prefix that is also a suffixlps[i]\text{lps}[i]
0AAEmpty0
1BABEmpty0
2AABAA1
3BABABAB2
4AABABAABA3
5CABABACEmpty0
6AABABACAA1

Thus, lps=[0,0,1,2,3,0,1]\text{lps} = [0, 0, 1, 2, 3, 0, 1]. This array is also commonly called the prefix function and written as π\pi.

Building LPS

Initialize all entries to 00. Start at index i=1i = 1, since a single character has no nonempty proper prefix.

Let jj be the length of the current candidate border. At the start of each iteration, it equals lps[i−1]\text{lps}[i - 1]. Try to extend that border with pattern[i]\text{pattern}[i]:

  1. While j>0j > 0 and pattern[i]≠pattern[j]\text{pattern}[i] \ne \text{pattern}[j], set j=lps[j−1]j = \text{lps}[j - 1] to try the next shorter border.
  2. If pattern[i]=pattern[j]\text{pattern}[i] = \text{pattern}[j], increment jj.
  3. Store lps[i]=j\text{lps}[i] = j and move to the next index.

Why use lps[j−1]\text{lps}[j - 1]? If a border of length jj cannot be extended, a shorter candidate must itself be both a prefix and a suffix of that border. Its longest such border is already stored at lps[j−1]\text{lps}[j - 1]. Repeating this step tries all viable candidates from longest to shortest.

For the example above, computing lps[5]\text{lps}[5] demonstrates multiple fallbacks:

  • The previous border is ABA, so j=3j = 3. Compare C with pattern[3]=B\text{pattern}[3] = \texttt{B}: mismatch.
  • Fall back to j=lps[2]=1j = \text{lps}[2] = 1. Compare C with pattern[1]=B\text{pattern}[1] = \texttt{B}: mismatch.
  • Fall back to j=lps[0]=0j = \text{lps}[0] = 0. Compare C with pattern[0]=A\text{pattern}[0] = \texttt{A}: mismatch.
  • No border can be extended, so lps[5]=0\text{lps}[5] = 0.

Notice that ii stays at 55 throughout these fallbacks.

Searching the Text

After building LPS, scan the text from left to right. Let ii be the current text index and jj be the number of pattern characters already matched.

Before comparing text[i]\text{text}[i], the previous jj text characters equal pattern[:j]\text{pattern}[:j]. This is the invariant that lets us reuse earlier comparisons.

  1. While j>0j > 0 and text[i]≠pattern[j]\text{text}[i] \ne \text{pattern}[j], set j=lps[j−1]j = \text{lps}[j - 1]. Keep ii fixed while trying shorter borders.
  2. If text[i]=pattern[j]\text{text}[i] = \text{pattern}[j], increment jj.
  3. If j=mj = m, the whole pattern matches. Its starting index is i−m+1i - m + 1.
  4. Otherwise, move to the next text character. If the comparison failed even at j=0j = 0, this simply skips the current text character.

On a mismatch, any overlapping match must begin with a suffix of the characters we already matched. That suffix must also be a prefix of the pattern, so the LPS fallback preserves the longest possible candidate without skipping a valid match.

Search Dry Run

For text=ABABABACA\text{text} = \texttt{ABABABACA} and pattern=ABABACA\text{pattern} = \texttt{ABABACA}, use lps=[0,0,1,2,3,0,1]\text{lps} = [0, 0, 1, 2, 3, 0, 1]:

Text index iitext[i]\text{text}[i]jj before comparisonActionjj after
0A0Match pattern[0]\text{pattern}[0]1
1B1Match pattern[1]\text{pattern}[1]2
2A2Match pattern[2]\text{pattern}[2]3
3B3Match pattern[3]\text{pattern}[3]4
4A4Match pattern[4]\text{pattern}[4]5
5B5Mismatch with C; fall back to 33, then match B4
6A4Match pattern[4]\text{pattern}[4]5
7C5Match pattern[5]\text{pattern}[5]6
8A6Match pattern[6]\text{pattern}[6]; the whole pattern has matched7

The answer is 8−7+1=28 - 7 + 1 = 2.

Implementation

These reusable functions return the first match or −1-1. We define an empty pattern to match at index 00, including in an empty text, and handle it before indexing the pattern.

vector<int> buildLPS(const string& pattern) {
    // Allocate one border length for each pattern position.
    int m = pattern.size();
    vector<int> lps(m, 0);
    // Track the length of the border we are trying to extend.
    int j = 0;
    // A single character has no nonempty proper border, so lps[0] is 0.
    for (int i = 1; i < m; i++) {
        // Try shorter borders until we can extend one.
        // The next candidate is the longest border of the current border.
        while (j > 0 && pattern[i] != pattern[j]) j = lps[j - 1];
        // A matching character extends the candidate border by one.
        if (pattern[i] == pattern[j]) j++;
        // Store the longest border for the prefix ending at i.
        lps[i] = j;
    }
    return lps;
}

int kmpSearch(const string& text, const string& pattern) {
    // An empty pattern matches at the beginning of any text.
    if (pattern.empty()) return 0;
    int n = text.size(), m = pattern.size();
    // Preprocess the pattern to reuse matches after a mismatch.
    vector<int> lps = buildLPS(pattern);
    // Track how many pattern characters have already matched.
    int j = 0;
    // Scan the text without ever moving i backward.
    for (int i = 0; i < n; i++) {
        // Retry this text character with a shorter matched prefix.
        // Keep the longest border of the matched prefix.
        while (j > 0 && text[i] != pattern[j]) j = lps[j - 1];
        // Extend the match if the current characters are equal.
        if (text[i] == pattern[j]) j++;
        // A complete match ends at i; convert it to its starting index.
        if (j == m) return i - m + 1;
    }
    // The scan ended without matching the whole pattern.
    return -1;
}

To solve LeetCode 28, keep the helpers above and call kmpSearch(haystack, needle) or kmp_search(haystack, needle) from Solution.strStr.

Some useful cases to check:

TextPatternFirst matchReason
ABABABACAABABACA22Reuses a border after a mismatch
AAAAAAAA00Returns the earliest occurrence
ABCD−1-1No match
ABABC−1-1Pattern is longer than the text
EmptyA−1-1No characters to match
ABCEmpty00Empty-pattern convention
EmptyEmpty00Empty-pattern convention

Finding All Occurrences

To find every match, record i−m+1i - m + 1 instead of returning immediately. Then set j=lps[m−1]j = \text{lps}[m - 1] and continue to the next text character. This keeps the longest border of the completed match, allowing overlapping matches.

For example, text=AAAAA\text{text} = \texttt{AAAAA} and pattern=AAA\text{pattern} = \texttt{AAA} have matches at indices [0,1,2][0, 1, 2]. After matching the first AAA, retain j=lps[2]=2j = \text{lps}[2] = 2. The next A completes another match. Resetting jj to 00 would lose these overlapping occurrences.

The following Python function reuses build_lps from above. For an empty pattern, it returns every boundary of the text, from 00 through nn.

def kmp_find_all(text: str, pattern: str) -> list[int]:
    # An empty pattern matches at every boundary, including after the text.
    if not pattern: return list(range(len(text) + 1))
    # Preprocess the pattern to reuse matches after a mismatch.
    lps = build_lps(pattern)
    # Collect the starting index of every occurrence.
    matches = []
    # Track how many pattern characters have already matched.
    j = 0
    # Scan each text character, continuing after complete matches.
    for i, char in enumerate(text):
        # Retry this character with shorter borders of the matched prefix.
        while j > 0 and char != pattern[j]: j = lps[j - 1]
        # Extend the match if the current characters are equal.
        if char == pattern[j]: j += 1
        # Record the starting index when the whole pattern matches.
        if j == len(pattern):
            matches.append(i - len(pattern) + 1)
            # Preserve a possible overlapping match.
            j = lps[j - 1]
    return matches

Complexity Analysis

  • LPS construction: O(m)O(m) time. The outer loop advances ii through the pattern. Each successful extension increases jj by one, while every fallback strictly decreases it. Since jj never becomes negative, the total number of fallbacks cannot exceed the total number of increases.
  • Searching: O(n)O(n) time. The text index advances once per outer iteration. As with LPS construction, jj increases at most once per text character, so there can be only O(n)O(n) fallback steps across the entire scan.
  • Total: O(n+m)O(n + m) time and O(m)O(m) auxiliary space for the LPS array. The nested while loops do not make the algorithm quadratic.

Finding all occurrences takes O(n+m+k)O(n + m + k) time and O(m+k)O(m + k) space when storing kk results. For a nonempty pattern, kk is at most nn, so the time remains O(n+m)O(n + m).

Common Mistakes

  • Falling back to lps[j]\text{lps}[j] instead of lps[j−1]\text{lps}[j - 1]. If jj characters matched, the matched prefix ends at index j−1j - 1.
  • Using only one fallback. A shorter border may also fail, so use while, not just if.
  • Advancing the text index during a fallback. Retry the current character against the shorter prefix before moving on.
  • Keeping j=mj = m while continuing the search. After recording a full match, fall back before the next comparison to avoid indexing past the pattern.
  • Confusing a length with an index. jj counts matched characters; a complete match ending at ii starts at i−m+1i - m + 1.

On this page

Your logo here
Your logo here
Your logo here
Become a sponsor