3.8.1 · D4String Algorithms

Exercises — Naive pattern matching — O(nm)

2,463 words11 min readBack to topic

Before we begin, every symbol used on this page, defined once in plain words:


Level 1 — Recognition

Exercise 1.1

For "XYZXYZ" and "XYZ", how many candidate shifts exist, and what are they? (Not asking which match — just how many positions we are allowed to try.)

Recall Solution

WHAT: count candidate starts. WHY: may only start where all of its characters still fit inside — beyond that it runs off the end. Here , . The formula from the parent note is . The candidate shifts are . WHAT IT LOOKS LIKE: look at figure below — the ruler XYZ can sit at 4 different starting positions before its right edge would poke past the end of XYZXYZ.

Exercise 1.2

In the trace, we say a shift "fails at ". What does the number mean here, in plain words?

Recall Solution

is the inner-loop counter — it walks across the pattern one letter at a time, . "Fails at " means: matched , matched , but did not match . So two characters matched and the third broke the streak. We then stop (early break) and slide to .


Level 2 — Application

Exercise 2.1

Fully trace naive matching for "AABAACAADAABAAABAA" and "AABA". Report the count of comparisons at the first shift and list all valid shifts. Use the Substring Search Problem convention (0-indexed).

Recall Solution

, , so shifts . Write out with indices: At : compare AABA against AABA. A=A, A=A, B=B, A=A → 4 comparisons, MATCH. So is valid. Walking the rest (mismatches stop early):

  • : ABAA vs AABA → A=A, B≠A, fail at .
  • : AABA → MATCH.
  • : AAAB → A=A, A=A, A≠B, fail at .
  • : AABA → MATCH.
  • All other shifts fail on the first or second character. Valid shifts: . Comparisons at : (the full pattern, because it matched).

Exercise 2.2

For the same as 2.1, how many candidate shifts are there, and what is the theoretical upper bound on total comparisons?

Recall Solution

Candidates: . Upper bound on comparisons: . This is the ceiling — the early break means the real count is usually far below it.


Level 3 — Analysis

Exercise 3.1

For "AAAAA" () and "AAB" (), count the exact number of character comparisons the algorithm performs (with early break). Then explain in one sentence why the break never helps here early.

Recall Solution

WHAT: hand-count every comparison. WHY: this exposes why "all-same-text + trailing mismatch" is the killer input. Candidate shifts: , so .

  • : AAB vs AAA → A=A (1), A=A (2), B≠A (3). Fails at after 3 comparisons.
  • : AAB vs AAA → 3 comparisons, fail.
  • : AAB vs AAA → 3 comparisons, fail. Total = comparisons. Note — the bound is hit exactly. Why break never saves us early: every character of the text is A, so the first letters of always match; only the final B mismatches. The break fires on the last comparison, so we still pay full each shift.

Exercise 3.2

Give the exact comparison count for the parent's worst case "AAAAAAAAA" (), "AAAB" ().

Recall Solution

Shifts: . Each shift matches AAA then fails on the last B, costing the full comparisons. Total = comparisons — matching the parent note's figure. This is made concrete.


Level 4 — Synthesis

Exercise 4.1

Design the best-case input for naive matching: choose and (both length-controllable) so the total comparison count is as small as possible, and give that count as a formula in and .

Recall Solution

WHAT: minimise total comparisons by choosing inputs. WHY: the outer loop always runs times (fixed); the only lever we control is the inner cost per shift. To make each shift cheap, force a mismatch on the very first comparison at every shift. Achieve this by making a character that never appears in . Example: "AAAAAAA", "BAA". At every shift, A B → fails immediately after 1 comparison. Formula: total comparisons, i.e. . WHAT IT LOOKS LIKE: the ruler slides all the way across but only ever peeks at its first letter each time — never spelling further.

Exercise 4.2

Modify the pseudocode so it stops after finding the first match instead of all matches. Give the new worst-case comparison count for a pattern that appears only at the very end of .

Recall Solution

Add a return s (or break on the outer loop) right after report match at s:

for s = 0 to n - m:
    j = 0
    while j < m and T[s+j] == P[j]:
        j = j + 1
    if j == m:
        return s        # stop at first match

Worst case for "match only at the end": the match sits at the last legal shift . We must try all earlier shifts first. If each earlier shift costs up to comparisons and the final matching shift costs : total still. Stopping early helps only when the match is early; if the sole match is at the tail, we do essentially the full worst-case work anyway.


Level 5 — Mastery

Exercise 5.1

An adversary must build the worst possible input over the alphabet A,B with fixed and . Construct and that force the maximum total comparisons, state that maximum, and prove no other choice beats it.

Recall Solution

WHAT: maximise total comparisons under fixed . WHY: the outer count is fixed; the adversary's only weapon is making every shift cost the full . To cost at a shift, the first characters must match and the last must mismatch (a full-length near-miss). Choose "AAB" and "AAAAAAAAAAAA" (twelve As). At every shift AAB vs AAA: A=A, A=A, B≠A → 3 comparisons, and it never actually matches, so the outer loop never short-circuits. Maximum total comparisons. Proof it's optimal: no shift can ever cost more than comparisons (the inner loop is bounded by ), and there are only shifts. Hence total for any input. Our construction attains , so it is a maximiser. ∎

Exercise 5.2

Two inputs both have . Input X = ("ABABABABAB", "AB"). Input Y = ("AAAAAAAAAA", "AB"). Which does more total comparisons, and why does the "many matches" input NOT automatically mean more work?

Recall Solution

Shifts for both: , so . Input X (ABABABABAB): at even , AB=AB → 2 comparisons + MATCH. At odd , BA → fail at , 1 comparison. Even shifts : five of them, each 2 comparisons = . Odd shifts : four of them, each 1 comparison = . X total comparisons (with 5 matches). Input Y (AAAAAAAAAA, AB): every shift → A=A (1), B≠A (2) → 2 comparisons, 0 matches. Y total comparisons (with 0 matches). Conclusion: Y does more work () despite finding zero matches, while X finds five matches with less work. Why "many matches" ≠ "more work": cost is driven by how deep each comparison chain goes before stopping, not by whether it ends in a match. In X, odd shifts die instantly (); in Y, every shift is forced to the last character. Depth of near-misses, not number of successes, sets the bill.


Active Recall

Recall lines:

Largest possible total comparisons for fixed n and m
, reached when every shift matches the first chars then fails on the last (e.g. text all A, pattern AA...AB).
Cheapest run: what makes each shift cost only 1 comparison
The pattern's first character never appears in the text, so every shift mismatches at ; total .
Does "return at first match" change worst-case Big-O
No — an adversary puts the only match at the last shift, so you still scan everything: worst case stays .
Why can a zero-match input cost more than a many-match input
Cost is comparison depth per shift; long last-character near-misses (all deep, no success) can exceed cheap matches plus quick mismatches.

Connections