Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Data Structures & Algorithms (DSA)  Module 16 – String algorithms

Knuth-Morris-Pratt and the prefix function

How the prefix function records the borders of a pattern, and how Knuth-Morris-Pratt (KMP) uses it to search a text in O(n + m) without moving back.

  • Advanced
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free core, Pro depth: 10 of 30 minutes free
Free: Where the direct search wastes work · The prefix function, step by step · Searching without moving back · Practise and check yourself. With a pass: 3 in-depth parts and 3 interview answers, about 20 minutes.

What you will learn

  • Compute the prefix (failure) function of a pattern in O(m)
  • Search a text in O(n + m) without moving back in it
  • Use the prefix function to find borders and the smallest period
  • Prove that the search is correct and runs in linear time
  • Implement KMP in Python, JavaScript, Java, C++ and Go

Before you start

The Knuth-Morris-Pratt algorithm, KMP for short, finds every occurrence of a pattern of length m in a text of length n in O(n + m) time, and it never moves back in the text. It first computes the prefix function of the pattern: pi[i] is the length of the longest proper prefix of p[0..i] that is also a suffix of it, called its longest border. During the search, a mismatch after j matched characters does not send the pattern back to the start: the last pi[j − 1] characters read are already a prefix of the pattern, so the search continues from there. Knuth, Morris and Pratt published the method in 1977, and the ACM, IEEE and AAAI computing curricula of 2023 name it, with Boyer-Moore, as an example of efficient string matching. This lesson builds the table step by step, traces a search and counts the work saved.

Where the direct search wastes work

The direct search tries every start and, after a mismatch, slides the pattern one place and compares again from its first character, so it reads the same text characters over and over. Search for 199 copies of a and then b in a text of 20,000 copies of a: every start matches 199 characters before it fails, and the next start reads 198 of them again. The run below counts the comparisons of both methods on the previous lesson’s texts:

Comparisons of the direct search and of KMP on three texts Python · naive/rereads.py
# Character comparisons of the direct search and of KMP on the same three texts. The direct search starts again one
# place further on after every mismatch and reads those characters again; KMP reads each text character once and
# compares it at most a few times.
import random


def prefix_function(p):
    pi = [0] * len(p)
    for i in range(1, len(p)):
        k = pi[i - 1]
        while k > 0 and p[i] != p[k]:
            k = pi[k - 1]
        if p[i] == p[k]:
            k += 1
        pi[i] = k
    return pi


def direct(text, pattern):
    n, m = len(text), len(pattern)
    found, compared = [], 0
    for i in range(n - m + 1):
        k = 0
        while k < m:
            compared += 1
            if text[i + k] != pattern[k]:
                break
            k += 1
        if k == m:
            found.append(i)
    return found, compared


def kmp(text, pattern):
    pi, m = prefix_function(pattern), len(pattern)
    found, compared, j = [], 0, 0
    for i, c in enumerate(text):
        while True:  # every comparison of c with a pattern character is counted once
            compared += 1
            if c == pattern[j]:
                j += 1
                break
            if j == 0:
                break
            j = pi[j - 1]
        if j == m:
            found.append(i - m + 1)
            j = pi[m - 1]
    return found, compared


rng = random.Random(5)
cases = [
    ("random DNA, n = 20,000, m = 200", "".join(rng.choices("ACGT", k=20_000)), "".join(rng.choices("ACGT", k=200))),
    ("a * 20,000 against a * 199 + b", "a" * 20_000, "a" * 199 + "b"),
    ("a * 20,000 against a * 200", "a" * 20_000, "a" * 200),
]
for name, text, pattern in cases:
    (f1, c1), (f2, c2) = direct(text, pattern), kmp(text, pattern)
    assert f1 == f2
    print(f"{name}: {len(f1):,} occurrences; direct search {c1:,} comparisons, KMP {c2:,} (2n = {2 * len(text):,})")

Output

random DNA, n = 20,000, m = 200: 0 occurrences; direct search 26,308 comparisons, KMP 24,938 (2n = 40,000)
a * 20,000 against a * 199 + b: 0 occurrences; direct search 3,960,200 comparisons, KMP 39,801 (2n = 40,000)
a * 20,000 against a * 200: 19,801 occurrences; direct search 3,960,200 comparisons, KMP 20,000 (2n = 40,000)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 rereads.py

On the second text the direct search makes 3,960,200 comparisons and KMP 39,801. On the third, where the pattern occurs at all 19,801 starts, KMP needs 20,000 comparisons, one per character, while Rabin-Karp had to verify every occurrence in full. Knuth-Morris-Pratt stays within 2n comparisons on every input, because it remembers how much of the pattern matched.

The prefix function, step by step

A border of a string is a proper prefix that is also a suffix: “ab” is a border of “abab”, and “” is a border of every string. The prefix function stores, for every prefix of the pattern, the length of its longest border:

i Letter Longest border pi
0 a none 0
1 b none 0
2 a a 1
3 b ab 2
4 c none 0
5 a a 1
6 b ab 2
7 a aba 3
8 b abab 4

It is computed from left to right. To find pi[i], take k = pi[i − 1], the longest border of the previous prefix, and try to grow it by one character: when p[i] equals p[k], pi[i] = k + 1. When they differ, the table tries the next shorter border, pi[k − 1], which is a border of the border, and keeps stepping down until one grows or k is 0. Run the trace and change the patterns to see every fallback:

The prefix function, one position at a time Python · table/prefix_table.py
# The prefix function, built one position at a time. pi[i] is the length of the longest proper prefix of p[0..i]
# that is also a suffix of it. Change PATTERNS and run again to trace your own strings.
PATTERNS = ["ababcabab", "aabaaab"]


def prefix_function(p, trace=False):
    pi = [0] * len(p)
    for i in range(1, len(p)):
        k = pi[i - 1]  # the longest border of p[0..i-1]: try to extend it by p[i]
        steps = []
        while k > 0 and p[i] != p[k]:
            steps.append(f"p[{k}]={p[k]} ≠ {p[i]}, fall back to pi[{k - 1}]={pi[k - 1]}")
            k = pi[k - 1]
        if p[i] == p[k]:
            steps.append(f"p[{k}]={p[k]} = {p[i]}, extend to {k + 1}")
            k += 1
        else:
            steps.append(f"p[{k}]={p[k]} ≠ {p[i]}, no border")
        pi[i] = k
        if trace:
            print(f"  i={i} {p[i]}: start at k={pi[i - 1]}; " + "; ".join(steps) + f" → pi[{i}] = {k}")
    return pi


for p in PATTERNS:
    print(f"pattern {p}")
    pi = prefix_function(p, trace=True)
    print(f"  pi = {pi}")

Output

pattern ababcabab
  i=1 b: start at k=0; p[0]=a ≠ b, no border → pi[1] = 0
  i=2 a: start at k=0; p[0]=a = a, extend to 1 → pi[2] = 1
  i=3 b: start at k=1; p[1]=b = b, extend to 2 → pi[3] = 2
  i=4 c: start at k=2; p[2]=a ≠ c, fall back to pi[1]=0; p[0]=a ≠ c, no border → pi[4] = 0
  i=5 a: start at k=0; p[0]=a = a, extend to 1 → pi[5] = 1
  i=6 b: start at k=1; p[1]=b = b, extend to 2 → pi[6] = 2
  i=7 a: start at k=2; p[2]=a = a, extend to 3 → pi[7] = 3
  i=8 b: start at k=3; p[3]=b = b, extend to 4 → pi[8] = 4
  pi = [0, 0, 1, 2, 0, 1, 2, 3, 4]
pattern aabaaab
  i=1 a: start at k=0; p[0]=a = a, extend to 1 → pi[1] = 1
  i=2 b: start at k=1; p[1]=a ≠ b, fall back to pi[0]=0; p[0]=a ≠ b, no border → pi[2] = 0
  i=3 a: start at k=0; p[0]=a = a, extend to 1 → pi[3] = 1
  i=4 a: start at k=1; p[1]=a = a, extend to 2 → pi[4] = 2
  i=5 a: start at k=2; p[2]=b ≠ a, fall back to pi[1]=1; p[1]=a = a, extend to 2 → pi[5] = 2
  i=6 b: start at k=2; p[2]=b = b, extend to 3 → pi[6] = 3
  pi = [0, 1, 0, 1, 2, 2, 3]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 prefix_table.py

At i = 4 the character c fails to extend “ab”, falls back to the empty border and gets 0. In “aabaaab”, position 5 falls back from 2 to 1 before it extends. The table costs O(m) time, because k rises by at most one per position and every fallback lowers it. One value says even more: m − pi[m − 1] is the smallest period of the pattern, the smallest shift under which it agrees with itself, so “abcabcab”, with pi[7] = 5, has period 3.

Searching without moving back

The search keeps j, the number of pattern characters that match the text read so far. Each text character either extends the match, or j falls back along the same chain of borders until the character fits or j is 0. After a full match the search continues from pi[m − 1], so overlapping occurrences are found too.

KMP searching ababc in abababcab: after abab matches and c fails, it falls back to the border ab and continues at the same text character.Text a b a b a b c a bPattern a b a b cj = 4: a b a b matchedText character 4 is a,pattern character 4 is c: mismatchThe matched part a b a b has the border a bpi[3] = 2: a b is both its prefix and its suffixText a b a b a b c a bPattern a b a b cj = 2: compare the same text a with pattern[2] = aa, b, c match: j = 5, an occurrence at 2no text character was read twicefall backslide the pattern 2 places

A mismatch, and the border that saves the work

Text description of the diagram

The diagram runs from top to bottom.

  1. The pattern a b a b c is placed under the start of the text a b a b a b c a b. The first four characters match, so j = 4.
  2. Text character 4 is a, but pattern character 4 is c, a mismatch.
  3. The matched part a b a b has the border a b: it is both a prefix and a suffix of a b a b, and pi[3] = 2 records that.
  4. So the pattern slides 2 places, putting its prefix a b under the last two text characters that matched, and j becomes 2. The same text character a is now compared with pattern character 2, which is also a.
  5. The next characters, a, b and then c, match as well: j reaches 5, and the pattern occurs at index 2. The search never moved back in the text.
KMP search, traced, then checked on 2,000 random texts Python · search/kmp_search.py
# Knuth-Morris-Pratt search: j is how many pattern characters match the text read so far. On a mismatch j falls back
# to pi[j - 1], the longest border of what matched, and the text index never moves back.
import random


def prefix_function(p):
    pi = [0] * len(p)
    for i in range(1, len(p)):
        k = pi[i - 1]
        while k > 0 and p[i] != p[k]:
            k = pi[k - 1]
        if p[i] == p[k]:
            k += 1
        pi[i] = k
    return pi


def kmp_search(text, pattern, trace=False):
    """Start index of every occurrence of pattern in text, overlapping ones included (pattern not empty)."""
    pi = prefix_function(pattern)
    m, j, found = len(pattern), 0, []
    for i, c in enumerate(text):
        chain = [j]
        while j > 0 and c != pattern[j]:
            j = pi[j - 1]  # keep the longest border of the matched part
            chain.append(j)
        if c == pattern[j]:
            j += 1
        if trace:
            fell = f", after falling back {' → '.join(map(str, chain))}" if len(chain) > 1 else ""
            print(f"  text[{i}] = {c}: j = {j}{fell}" + (f": match at {i - m + 1}, j becomes pi[{m - 1}] = {pi[m - 1]}" if j == m else ""))
        if j == m:
            found.append(i - m + 1)
            j = pi[m - 1]
    return found


print("pi of abab:", prefix_function("abab"))
print("search abab in abacababab:")
print(kmp_search("abacababab", "abab", trace=True))

rng = random.Random(1)
for _ in range(2_000):
    text = "".join(rng.choices("ab", k=rng.randint(0, 30)))
    pattern = "".join(rng.choices("ab", k=rng.randint(1, 6)))
    assert kmp_search(text, pattern) == [i for i in range(len(text)) if text.startswith(pattern, i)]
print("2,000 random texts: the same occurrences as checking every start")

Output

pi of abab: [0, 0, 1, 2]
search abab in abacababab:
  text[0] = a: j = 1
  text[1] = b: j = 2
  text[2] = a: j = 3
  text[3] = c: j = 0, after falling back 3 → 1 → 0
  text[4] = a: j = 1
  text[5] = b: j = 2
  text[6] = a: j = 3
  text[7] = b: j = 4: match at 4, j becomes pi[3] = 2
  text[8] = a: j = 3
  text[9] = b: j = 4: match at 6, j becomes pi[3] = 2
[4, 6]
2,000 random texts: the same occurrences as checking every start

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 kmp_search.py

At text[3] the character c breaks a match of “aba”: j falls back 3 → 1 → 0, and the text index stays where it is. After the match at 4, j becomes pi[3] = 2, so the next two characters complete the overlapping match at 6. Every fallback lowers j, and j rises at most once per text character, so the search makes at most about 2n comparisons and costs O(n) after the O(m) table.

Two further tools come from the same table. A refined table skips a border when the character after it equals the one that just failed, which limits the work spent on a single text character. And the chain of borders lists every border of a string, which gives its periods and tells whether it is a shorter string repeated.

Practise and check yourself

Count overlapping occurrences and find the smallest period. The tests compare your answers with direct checks on random strings and use texts of 200,000 characters.

Exercise · Medium · Python, JavaScript

Count occurrences and find the smallest period with KMP

Write two functions in kmp_tools.py (JavaScript: kmp_tools.mjs), both built on the prefix function.

  1. count_occurrences(text, pattern) (JavaScript: countOccurrences) returns how many times pattern occurs in text, overlapping occurrences included: "aa" occurs 3 times in "aaaa". The pattern has at least one character. Search with KMP, so that no text character is read again after a mismatch.
  2. smallest_period(s) (JavaScript: smallestPeriod) returns the smallest p ≥ 1 such that s[i] == s[i + p] for every i where both exist. s has at least one character. For "abcabcab" the answer is 3; for "abcd" it is 4, the length itself.

The tests compare your answers with a direct check on random strings and use texts of up to 200,000 characters.

Python · Starter code · kmp_tools.py

def count_occurrences(text, pattern):
    """How many times pattern occurs in text, overlapping occurrences included, found with KMP."""
    # Replace this line with your code.
    return 0


def smallest_period(s):
    """The smallest p >= 1 with s[i] == s[i + p] wherever both exist."""
    # Replace this line with your code.
    return len(s)
The sample tests · test_kmp_tools.py
import random

from kmp_tools import count_occurrences, smallest_period


def direct_count(text, pattern):
    return sum(text.startswith(pattern, i) for i in range(len(text)))


def direct_period(s):
    return next(p for p in range(1, len(s) + 1) if all(s[i] == s[i + p] for i in range(len(s) - p)))


def test_small_cases():
    """overlapping occurrences and periods of short strings"""
    assert count_occurrences("aaaa", "aa") == 3
    assert count_occurrences("abacababab", "abab") == 2
    assert count_occurrences("abc", "abcd") == 0
    assert count_occurrences("", "a") == 0
    assert smallest_period("abcabcab") == 3
    assert smallest_period("abcd") == 4
    assert smallest_period("aaaa") == 1
    assert smallest_period("abaab") == 3
    assert smallest_period("x") == 1


def test_random_strings():
    """500 random strings over two letters, against direct checks"""
    rng = random.Random(6)
    for _ in range(500):
        text = "".join(rng.choices("ab", k=rng.randint(0, 40)))
        pattern = "".join(rng.choices("ab", k=rng.randint(1, 6)))
        assert count_occurrences(text, pattern) == direct_count(text, pattern), (text, pattern)
        s = "".join(rng.choices("ab", k=rng.randint(1, 20)))
        assert smallest_period(s) == direct_period(s), s


def test_long_inputs():
    """200,000 characters: a repeated letter, and a period found in one pass"""
    assert count_occurrences("a" * 200_000, "a" * 1_000) == 199_001
    assert count_occurrences("ab" * 100_000, "abba") == 0
    assert smallest_period("abc" * 66_666 + "ab") == 3
    assert smallest_period("a" * 199_999 + "b") == 200_000

JavaScript · Starter code · kmp_tools.mjs

/** How many times pattern occurs in text, overlapping occurrences included, found with KMP. */
export function countOccurrences(text, pattern) {
  // Replace this line with your code.
  return 0;
}

/** The smallest p >= 1 with s[i] === s[i + p] wherever both exist. */
export function smallestPeriod(s) {
  // Replace this line with your code.
  return s.length;
}
The sample tests · kmp_tools.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { countOccurrences, smallestPeriod } from './kmp_tools.mjs';

function directCount(text, pattern) {
  let n = 0;
  for (let i = 0; i < text.length; i++) if (text.startsWith(pattern, i)) n++;
  return n;
}

function directPeriod(s) {
  for (let p = 1; p <= s.length; p++) {
    let ok = true;
    for (let i = 0; i + p < s.length && ok; i++) ok = s[i] === s[i + p];
    if (ok) return p;
  }
  return s.length;
}

function makeRandom(seed) {
  return () => (seed = (seed * 48271) % 2147483647) / 2147483647;
}

function randomWord(random, min, max) {
  const n = min + Math.floor(random() * (max - min + 1));
  let s = '';
  for (let i = 0; i < n; i++) s += random() < 0.5 ? 'a' : 'b';
  return s;
}

test('overlapping occurrences and periods of short strings', () => {
  assert.equal(countOccurrences('aaaa', 'aa'), 3);
  assert.equal(countOccurrences('abacababab', 'abab'), 2);
  assert.equal(countOccurrences('abc', 'abcd'), 0);
  assert.equal(countOccurrences('', 'a'), 0);
  assert.equal(smallestPeriod('abcabcab'), 3);
  assert.equal(smallestPeriod('abcd'), 4);
  assert.equal(smallestPeriod('aaaa'), 1);
  assert.equal(smallestPeriod('abaab'), 3);
  assert.equal(smallestPeriod('x'), 1);
});

test('500 random strings over two letters, against direct checks', () => {
  const random = makeRandom(6);
  for (let t = 0; t < 500; t++) {
    const text = randomWord(random, 0, 40);
    const pattern = randomWord(random, 1, 6);
    assert.equal(countOccurrences(text, pattern), directCount(text, pattern), `${text} ${pattern}`);
    const s = randomWord(random, 1, 20);
    assert.equal(smallestPeriod(s), directPeriod(s), s);
  }
});

test('200,000 characters: a repeated letter, and a period found in one pass', () => {
  assert.equal(countOccurrences('a'.repeat(200_000), 'a'.repeat(1_000)), 199_001);
  assert.equal(countOccurrences('ab'.repeat(100_000), 'abba'), 0);
  assert.equal(smallestPeriod('abc'.repeat(66_666) + 'ab'), 3);
  assert.equal(smallestPeriod('a'.repeat(199_999) + 'b'), 200_000);
});
A hint

Compute pi for the pattern first. While scanning the text keep j, the number of pattern characters matched; on a mismatch set j = pi[j - 1] until the character matches or j is 0. After a full match, set j = pi[m - 1] rather than 0, or overlapping occurrences are lost. The smallest period of s is len(s) - pi[len(s) - 1].

The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Text Repeater Make worst cases to time your search: 'a' repeated 100,000 times, then a pattern of 999 copies of 'a' followed by 'b'.

Check yourself

4 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 4 What is the last value of the prefix function of "abcabcab", that is, the length of its longest proper prefix that is also a suffix?

    Type a number.

    Show the answer to question 1

    Answer: 5 characters

    "abcab" is both a prefix and a suffix of "abcabcab", and no longer proper prefix is: "abcabc" is not a suffix. So pi[7] = 5, and the smallest period is 8 − 5 = 3.

  2. Question 2 of 4 KMP has matched j = 4 characters of the pattern "ababc" when the next text character fails to match. What does it do?

    Choose one answer.

    Show the answer to question 2

    Answer: Sets j to pi[3] = 2 and compares the same text character with pattern[2]

    The four matched characters are "abab", whose longest border is "ab", so pi[3] = 2. Those two characters are already known to match the text, so the search continues with j = 2 at the same text position; it never moves back in the text.

  3. Question 3 of 4 After a full match of a pattern of length m, why does KMP set j to pi[m − 1] instead of 0?

    Choose one answer.

    Show the answer to question 3

    Answer: So that an occurrence overlapping the one just found is not missed

    The end of one occurrence can be the start of the next: "aa" occurs in "aaaa" at 0, 1 and 2. Keeping the longest border of the whole pattern keeps those characters counted as matched; starting again from 0 would find only 0 and 2.

  4. Question 4 of 4 Why does the KMP search make at most about 2n character comparisons on a text of length n?

    Choose one answer.

    Show the answer to question 4

    Answer: Each successful comparison raises j by one, at most once per text character, and each failed comparison that is not the last one for its character lowers j

    A single text character can be compared many times, so the bound is not per character. But j rises at most n times in total, and every fallback lowers it, so there are at most n fallbacks. Adding one final comparison per character gives at most about 2n comparisons in all.

Why KMP is correct and linear, with every comparison counted Pro: opens with a Pro, Premium or Ultimate pass

The search is short, and so is the proof that it never misses an occurrence and never does more than about 2n comparisons. This part proves both, counts the comparisons on four extreme texts and measures the worst delay a single character can cause, with the plain table and with the refined one.

In this part:

  • Why falling back to pi[k − 1] skips no border: the chain lemma
  • The invariant of the search, and why it finds every occurrence
  • Counting the comparisons on four extreme texts
  • The worst delay of one character, measured

1 runnable example (Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Borders, periods and repeated phrases, proved and counted Pro: opens with a Pro, Premium or Ultimate pass

The last value of the table answers more than the search. This part proves the period formula and the test for a repeated string, counts how often every prefix occurs, checks all of it on 8,190 strings, and applies it to a melody and a rhythm.

In this part:

  • Why m − pi[m − 1] is the smallest period, with a proof
  • When a string is a shorter string repeated: the divisibility proof
  • How often every prefix occurs, from one table
  • A phrase in a melody and the period of a rhythm, counted

2 runnable examples (Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

KMP in five languages, and three bugs that pass small tests Pro: opens with a Pro, Premium or Ultimate pass

KMP fits in twenty lines in every interview language, and its bugs hide in single tokens. This part runs the same program in Python, JavaScript, Java, C++ and Go, reproduces three bugs by searching every short input, and ends with practice questions.

In this part:

  • The prefix function and the search in Python, JavaScript, Java, C++ and Go
  • Three bugs, each reproduced by the smallest input that exposes it
  • Practice questions, with answers

3 runnable examples (C++, Go, Java, JavaScript, Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher to mid · Warm-up

    What does the prefix-function value pi[i] mean, and what is the prefix function of "aabaa"?

    pi[i] is the length of the longest proper prefix of p[0..i] that is also a suffix of it, its longest border; proper means shorter than p[0..i] itself. For "aabaa" the values are 0, 1, 0, 1, 2: "a" has no proper border, "aa" has "a", "aab" has none, "aaba" has "a" and "aabaa" has "aa". KMP uses the table after a mismatch: if j characters matched, their last pi[j − 1] characters are already a prefix of the pattern, so the search continues with j = pi[j − 1] instead of starting again, and never moves back in the text. The whole table takes O(m) time, because each position either extends the previous border by one character or falls back along the chain of shorter borders.

  2. Fresher to mid

    Your KMP-based counter reports 2 occurrences of "aa" in "aaaa" and passes every other test you have. Find the bug, fix it, and argue that the fixed version can neither miss an occurrence nor count one twice.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    A gateway must flag every occurrence of a 2,000-byte signature in a stream that arrives in packets of up to 1,500 bytes, and it may keep only the current packet in memory. How does KMP find matches that span packets, and what is the most work a single byte can cost?

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    Given a string of up to 10⁶ characters, decide whether it is a shorter string repeated, and if so return the shortest such unit. Give a prefix-function solution, prove it, and compare it with searching for the string inside itself doubled.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • The prefix function gives, for each prefix of the pattern, the length of its longest proper prefix that is also a suffix, and it is computed in O(m) by extending or falling back along borders.
  • KMP keeps the number of matched characters; on a mismatch it falls back to pi[j − 1] and never moves back in the text, so the search costs O(n + m).
  • After a full match, continue from pi[m − 1], or overlapping occurrences are lost.
  • m − pi[m − 1] is the smallest period of a string of length m.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.