3.8.1 · D3String Algorithms

Worked examples — Naive pattern matching — O(nm)

2,590 words12 min readBack to topic

Two things we will count in every example:

  • Shift — the starting index in the text where we lay the pattern down. Think of it as "how many steps right have we slid the transparent ruler?"
  • Character comparisons — one comparison is checking a single letter against . This is the unit the cost is measured in.

The scenario matrix

Every input to a pattern matcher falls into one of these case classes. Our worked examples below are labelled with the cell they hit, and together they touch every row.

# Case class What makes it special Covered by
A Match in the middle ordinary case, break fires early on misses Ex 1
B Match at the very start () left edge, no sliding needed to find first hit Ex 1
C Match at the very end () right edge — pattern touches the last char Ex 2
D No match at all inner loop never reaches Ex 3
E Overlapping matches matches whose regions share characters Ex 4
F Degenerate: pattern is a single character Ex 5
G Degenerate: pattern as long as text — one shift only Ex 6
H Empty pattern () limiting/boundary input Ex 6
I Worst case early break never helps Ex 7
J Best/expected case mismatch on char 0 every time → Ex 8
K Real-world word problem DNA / text search framing Ex 9
L Exam twist — count comparisons exactly reason about cost, not just position Ex 10

Example 1 — middle + start matches (cells A, B)

Here , , so shifts run .

Figure — Naive pattern matching — O(nm)
  1. : compare "ABC" with "ABC" → A=A, B=B, C=C. All 3 match, reaches . MATCH at (cell B, left edge). Why this step? We start and walk the pattern; only when do we declare success.
  2. : "B" vs "A" → mismatch at . Break immediately. One comparison spent. Why this step? The early break kills the shift the instant a letter disagrees — no point checking "C" or "A" after.
  3. : "C" vs "A" → mismatch. Break.
  4. : "ABC" → match. MATCH at (cell A, middle).
  5. : "B" vs "A" → mismatch. Break.
  6. : "CAB" → C vs A mismatch. Break.

Answer: matches at .


Example 2 — match at the very end (cell C)

, , shifts .

  1. : the first letters "X","Y","Z","X","Y","Q" all differ from "P" except we must check each. At , "Q" vs "P" → mismatch. Every one of these breaks on the first char. Why this step? begins with "P", which appears in only near the end, so all early shifts die at .
  2. : "PQR" vs "PQR" → P=P, Q=Q, R=R. MATCH at . Why this step? is the last legal shift; the pattern's final char lands on , the last character of the text. Any larger would run off the end.

Answer: single match at .


Example 3 — no match anywhere (cell D)

, ? No — "WORLD" has length 5 and "HELLO" length 5, so . Shifts : only one candidate.

  1. : "H" vs "W" → mismatch at . Break. Why this step? With there's exactly one place to check, and the very first letter fails.
  2. Inner loop never reached , so no shift is reported.

Answer: no matches — empty result. Exactly 1 comparison was made.


Example 4 — overlapping matches (cell E)

, , shifts .

Figure — Naive pattern matching — O(nm)
  1. : "AA" → match. MATCH at .
  2. : "AA" → match. MATCH at . Why this step? used characters at indices ; uses indices . Index 1 is shared — the two matches overlap. Naive matching happily reports both because it treats each shift independently.
  3. : "AA" → match. MATCH at .

Answer: matches at — three overlapping occurrences.


Example 5 — degenerate: single-character pattern (cell F)

, , shifts (that's shifts — one per character).

  1. Each shift compares exactly one character: vs "A". Why this step? When the inner loop runs at most once, so "does this shift match?" collapses to "is this letter an A?"
  2. Letters of "BANANA" by index: B(0), A(1), N(2), A(3), N(4), A(5). Matches where the letter is "A": .

Answer: matches at .

Recall Why

makes cost exactly Every shift does comparison, and there are shifts, so total comparisons . The bound becomes — the search degenerates into a linear scan. ✔


Example 6 — degenerate lengths: and empty pattern (cells G, H)

(a) : shifts only one shift.

  1. : compare all 4 chars C=C, O=O, D=D, E=E → match. MATCH at . Why this step? When the pattern is as long as the text there's literally nowhere to slide; you check the single alignment and stop.

(b) (empty pattern): shifts shifts.

  1. The inner loop condition j < m is j < 0, false immediately, so it runs zero comparisons and j==m (both ) is instantly true. Why this step? An empty string is a substring of every position, including one past the last character. So the empty pattern "matches" at all positions .

Answers: (a) match at . (b) matches at (five positions).


Example 7 — the worst case (cell I)

, , shifts → 4 shifts.

Figure — Naive pattern matching — O(nm)
  1. Every shift: "A"=="A" ✓, "A"=="A" ✓, then "A" vs "B" ✗. Why this step? The first characters always match (all A's), so the break only fires on the last comparison. We pay the maximum comparisons per shift.
  2. Total comparisons. Why this step? This is exactly the product in the formula reached with no early exit — the algorithm's ceiling.

Answer: 12 comparisons; zero matches. This is where naive matching earns its reputation, motivating KMP Algorithm and Boyer-Moore Algorithm.


Example 8 — best / expected case (cell J)

, , shifts → 5 shifts.

  1. Every shift: vs "X" → mismatch immediately (no letter of "ABCDEF" is "X"). Break after 1 comparison. Why this step? When the first pattern character is rare/absent, the break fires at every time — the inner loop never gets going.
  2. Total comparisons.

Answer: 5 comparisons, no matches — this is the behaviour that makes naive matching perfectly fine on typical text. Contrast with Ex 7's 12: same-ish sizes, wildly different cost.


Example 9 — real-world word problem: DNA search (cell K)

, , shifts .

  1. : "GATTACA" MATCH. 7 comparisons (full pattern). Why this step? A successful shift always spends the full comparisons — it must confirm every letter.
  2. : "A" vs "G" → mismatch, break after 1.
  3. : each first letter ("T","T","A","C","A") differs from "G" → break after 1 comparison each.
  4. : "GATTACA" MATCH. 7 comparisons. Why this step? The second copy begins exactly at index 7; the two matches are non-overlapping because they abut ( covers , covers ).

Answer: motif found at and ; two non-overlapping occurrences. Total comparisons wait — count them: successes , plus failures at = 6 single comparisons → .


Example 10 — exam twist: count comparisons exactly (cell L)

, , shifts → 3 shifts.

  1. : "AB"="AB" → match. Comparisons: A=A ✓, B=B ✓ → 2 comparisons.
  2. : "B" vs "A" → mismatch. 1 comparison. Why this step? Break after the first letter; we never look at for this shift.
  3. : "AB"="AB" → match. 2 comparisons.

Answer: (a) matches at . (b) total comparisons. Note the upper bound ; we did 5, saving one thanks to the early break at .


Recap of the matrix

Recall Which example hit which cell?

Ex1 ::: A (middle) + B (start) Ex2 ::: C (right edge, ) Ex3 ::: D (no match) Ex4 ::: E (overlapping matches) Ex5 ::: F (single-char pattern, ) Ex6 ::: G () and H (empty pattern ) Ex7 ::: I (worst case ) Ex8 ::: J (best/expected ) Ex9 ::: K (real-world DNA search) Ex10 ::: L (exact comparison count, exam twist)

For text of length n and all-same-char pattern of length m inside all-same-char text, how many overlapping matches?
.
Where does an empty pattern (m=0) match in a text of length n?
At all positions through .
In the worst case with pattern "AA...B", how many comparisons per shift?
The full — the break fires only on the last character.
Why does pattern "XY" against "ABCDEF" cost only ~n comparisons?
The first pattern char never appears, so every shift breaks after 1 comparison (best/expected case).
When m=n, how many candidate shifts are there?
Exactly one ().

Connections