GeeksforGeeks Medium

Z Function

Compute Z[i] = length of the longest substring starting at i that matches a prefix of s, in O(n). Search patterns via pat$txt.

stringz-algorithmpattern-matching
Open on GeeksforGeeks ↗
02

Intuition

Maintain the rightmost match window [l, r] found so far (a segment known to equal a prefix). Inside it, position i mirrors position i−l of the prefix — copy Z[i−l] for free, capped at the window edge; only extend by direct comparison past r. Every comparison pushes r right, so total work is linear.

How to spot this pattern

The Z-array gives, for each position, the length of the longest prefix match starting there — and it's built in linear time by reusing work. The [l, r] window is a previously-matched block: inside it, an earlier Z-value predicts the answer for free, so comparisons only ever extend past r. That reuse-what-you-matched idea is the same one behind KMP.

03

Approach

1

The Z-box [l, r]

s[l..r] equals s[0..r−l]. For i inside it, the answer is already partially known from the prefix's own Z values.

2

Copy, cap, extend

Z[i] = min(Z[i−l], r−i+1) as a starting point; then compare characters beyond r to extend. Update [l, r] if i's match reaches further right.

3

Pattern search

Build Z on pat + '$ + txt: any Z value equal to len(pat) marks a full occurrence.

04

Solution & live demo

1def z_function(s):
2 n = len(s)
3 z = [0] * n
4 l = r = 0
5 for i in range(1, n):
6 if i <= r:
7 z[i] = min(r - i + 1, z[i - l])
8 while i + z[i] < n and s[z[i]] == s[i + z[i]]:
9 z[i] += 1
10 if i + z[i] - 1 > r:
11 l, r = i, i + z[i] - 1
12 return z
05

Common pitfalls

Comparing from scratch at every position

✗ Wrong
for i in range(1, n):
    while i + z[i] < n and s[z[i]] == s[i + z[i]]:
        z[i] += 1
✓ Right
if i <= r:
    z[i] = min(r - i + 1, z[i - l])
while i + z[i] < n and s[z[i]] == s[i + z[i]]:
    z[i] += 1

That's the O(n²) version — correct, but it re-derives matches already known. Inside the [l, r] window the text mirrors the prefix, so a previous Z-value gives a free starting length.

Omitting the r - i + 1 cap

✗ Wrong
z[i] = z[i - l]
✓ Right
z[i] = min(r - i + 1, z[i - l])

The mirrored value is only trustworthy as far as the window extends. Copying it wholesale asserts matches beyond r that were never verified, and the result is silently too large.

Starting the loop at index 0

✗ Wrong
for i in range(n):
✓ Right
for i in range(1, n):

z[0] would be the whole string matching itself, which is conventionally left as 0 and, worse, would set r to n - 1 immediately — making every later position think it sits inside a verified window.

06

Edge cases

All same characters

Z = [-, n−1, n−2, …] — windows chain perfectly, still linear.

Separator character

'$ must not appear in either string, guaranteeing no match spans it.

07

Complexity

Time
O(n)
Space
O(n)
r only moves right → amortized linear.