3.8.4String Algorithms

Z-algorithm — Z-array construction, O(n+m)

2,193 words10 min readdifficulty · medium5 backlinks

WHAT is the Z-array?

Concretely Z[i]=max{k:s[0..k1]=s[i..i+k1]}Z[i] = \max\{ k : s[0..k-1] = s[i..i+k-1] \}.


WHY does the naive version cost O(n2)O(n^2) — and how do we fix it?

Naive: for each ii, compare s[i+k]s[i+k] vs s[k]s[k] until mismatch. Worst case aaaa...a → each ii scans to the end → i=O(n2)\sum i = O(n^2).


HOW to build it — derivation from scratch

We maintain a window [l,r][l, r] = rightmost segment matching the prefix found so far (start l=r=0l=r=0).

For each ii from 11 to n1n-1, two cases:

Case A — i>ri > r (we're outside any known box). We have no information. Compare from scratch: Z[i]=max{k:s[k]=s[i+k]}Z[i] = \max\{k : s[k] = s[i+k]\} starting at k=0k=0. If Z[i]>0Z[i]>0, set the new box l=i, r=i+Z[i]1l=i,\ r=i+Z[i]-1.

Case B — iri \le r (we're inside the box). Let i=ili' = i - l be the mirror position in the prefix. We know Z[i]Z[i']. Two sub-cases:

  • B1: Z[i]<ri+1Z[i'] < r-i+1. The mirror's match fits entirely inside the box. Since inside the box ss equals the prefix exactly, the match at ii is identical: Z[i]=Z[i](no comparisons, no box update)Z[i] = Z[i']\quad(\text{no comparisons, no box update}) Why no comparison? Because the mismatch that ended the mirror's match is still inside the box, so it's guaranteed to repeat at ii.

  • B2: Z[i]ri+1Z[i'] \ge r-i+1. The mirror's match reaches the box boundary; beyond rr we have no guarantee, so we must compare manually starting from k=ri+1k = r-i+1: Z[i]=(ri+1)+(extra matches found by comparing s[k] vs s[i+k])Z[i] = (r-i+1) + (\text{extra matches found by comparing } s[k] \text{ vs } s[i+k]) Then update box to l=i, r=i+Z[i]1l=i,\ r=i+Z[i]-1.

Figure — Z-algorithm — Z-array construction, O(n+m)

WHY is the total cost O(n+m)O(n+m)?

Pattern matching application

To find pattern pp (length mm) in text tt (length nn): build string S=p+#+t(#p,t)S = p + \texttt{\#} + t \qquad (\texttt{\#}\notin p,t) Compute ZZ over SS (length m+1+nm+1+n). Wherever Z[i]=mZ[i] = m, the pattern occurs in tt at position i(m+1)i-(m+1). Total cost O(n+m)O(n+m).


Common mistakes


Recall Feynman: explain to a 12-year-old

Imagine the first few letters of a word are your "secret code". You walk along the rest of the word asking at each spot: "Starting here, how many letters match the start of the word?" The slow way is to re-check from zero each time. The clever trick: if you already matched a long chunk earlier, that chunk is a photocopy of the beginning — so a spot inside it must behave the same as the matching spot near the start. You just look up the answer you already wrote down, instead of re-counting. You only do new counting when you walk past the edge of the photocopied chunk — and since that edge only ever moves forward, you never do too much work.


Flashcards

What does Z[i] represent?
The length of the longest substring starting at index i that is also a prefix of s.
Why is naive Z computation O(n²)?
For strings like "aaa...a" each starting index re-scans to the end, summing to O(n²) comparisons.
What is the Z-box [l,r]?
The rightmost (largest r) interval already known to match the prefix: s[l..r] = s[0..r-l].
In Case B (i ≤ r), what is the mirror index?
i' = i - l; the corresponding position inside the prefix.
Why can we copy Z[i-l] inside the box without comparing?
Inside [l,r] the string equals the prefix, so s[i]=s[i-l]; the mirror's match (and its terminating mismatch) repeat exactly — if it stays within the box. :::
What does min(r-i+1, Z[i-l]) protect against?
Claiming matches beyond r that were never verified; it caps trust at the box boundary.
When do you update the box?
Only when i + Z[i] - 1 > r, i.e. the new match extends further right than the current box.
Why is the total cost O(n)?
Each inner while-iteration increases r by 1; r only grows and is ≤ n-1, so total extensions ≤ n.
How to do pattern matching with Z?
Build S = pattern + '#' + text, compute Z; positions where Z[i]=m (pattern length) are matches.
Why must the separator '#' be absent from both strings?
Otherwise a Z-match could span the boundary and report a false occurrence.

Connections

  • String Hashing — alternative O(n) substring-match tool; Z avoids hash collisions.
  • KMP failure function — also computes prefix-match info; Z and the prefix-function are inter-convertible.
  • Suffix Array / Suffix Automaton — heavier structures for repeated queries.
  • Amortized Analysis — the "rr only increases" argument is a potential-function classic.
  • Manacher's Algorithm — same "reuse the symmetric/mirror box" trick for palindromes.

Concept Map

defines

Zi = longest match with prefix

enables

glue pattern + text

worst case aaaa

mirror inside box

avoids re-comparison

i > r

i <= r

extends r

fixed by

String s length n

Z-array

Definition

Pattern matching

Find Z = pattern length

Naive compare

O n^2 cost

Z-box l..r rightmost

Reuse Z at i-l

O n+m linear

Case A compare from scratch

Case B copy mirror

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho, Z-algorithm ka kaam simple hai: har index ii ke liye batao ki wahan se shuru karke kitne characters string ke prefix se match karte hain. Yeh value Z[i] kehlati hai. Agar tum naive tarike se har position pe zero se compare karoge to worst case (jaise "aaaa...") mein O(n2)O(n^2) ho jata hai — bahut slow.

Trick yeh hai: jab tumne pehle se koi lamba match dhoond liya, to ek "Z-box" [l,r][l,r] banta hai jo prove karta hai ki s[l..r]s[l..r] bilkul prefix jaisa hai — yaani uska photocopy. Ab agar tumhe kisi ii ka Z chahiye jo is box ke andar hai, to box ke andar ka character s[i]s[i] exactly s[il]s[i-l] ke barabar hai. Iska matlab Z[i-l] (mirror position) ka answer tum free mein copy kar sakte ho — koi comparison nahi! Sirf jab tum box ke right edge rr se aage nikalte ho, tab actual comparison karte ho.

Aur yahi linear time ka raaz hai: har baar jab tum aage compare karte ho aur match milta hai, rr ek aage badh jata hai. rr kabhi peeche nahi jata aur nn se zyada nahi ho sakta — isliye total extra comparisons sirf nn tak. Bas yaad rakho: min(r-i+1, Z[i-l]) lagana zaroori hai, warna tum box ke bahar wale unverified matches claim kar baithoge.

Pattern matching ke liye to bilkul mast use hota hai: S=pattern+#+textS = pattern + \texttt{\#} + text banao, Z nikaalo, aur jahan Z[i]=mZ[i] = m (pattern length) wahan match mil gaya — sab O(n+m)O(n+m) mein. Competitive programming mein yeh ek must-know weapon hai.

Go deeper — visual, from zero

Test yourself — String Algorithms

Connections