The Z-array answers, for every index i: how many characters starting at i match the prefix of the string?
Z[i] = length of the longest common prefix of s and s[i:].
Focus on recognizing:
“Prefix match at every position” / “linear pattern search” → Z-array
Pattern 1: Build the Z-Array
Watch the Z-array fill for "aabaab" — Z[3]=3 exposes the repeated "aab". Press ▶ to animate.
⚠️ Animation & Content Notice
The animation work is not fully finished — some animations may have slight errors.
If there is a major error in the content or if the animation or content is difficult to understand, please contact us at rayyancodingschool@gmail.com.
Z-Algorithm
Z-array: length of the longest prefix of s starting at each index.
Z[i] = longest substring starting at i that matches a prefix of s. Maintain a z-box [l, r]; if i is inside, Z[i] ≥ min(r-i+1, Z[i-l]), then extend. Enables O(n) substring search and underpins many string tricks.
1
z[0] = n (by convention)
2
for i in 1..n-1:
3
if inside z-box: z[i] = min(r - i + 1, z[i - l])
4
extend match from i while s[z[i]] == s[i + z[i]]
5
if i + z[i] - 1 > r: update box [l, r]
The trick is reusing a Z-box [l, r] — the rightmost prefix-match seen so far:
public int[] zArray(String s) {
int n = s.length();
int[] z = new int[n];
int l = 0, r = 0; // current z-box
for (int i = 1; i < n; i++) {
if (i < r) { // inside box: reuse
z[i] = Math.min(r - i, z[i - l]);
}
while (i + z[i] < n
&& s.charAt(z[i]) == s.charAt(i + z[i]))
z[i]++; // extend match
if (i + z[i] > r) { // new rightmost box
l = i;
r = i + z[i];
}
}
return z;
}def z_array(s):
n = len(s)
z = [0] * n
l = r = 0 # current z-box
for i in range(1, n):
if i < r: # inside box: reuse
z[i] = min(r - i, z[i - l])
while i + z[i] < n and s[z[i]] == s[i + z[i]]:
z[i] += 1 # extend match
if i + z[i] > r: # new rightmost box
l, r = i, i + z[i]
return zvector<int> zArray(const string& s) {
int n = s.size();
vector<int> z(n, 0);
int l = 0, r = 0; // current z-box
for (int i = 1; i < n; i++) {
if (i < r) { // inside box: reuse
z[i] = min(r - i, z[i - l]);
}
while (i + z[i] < n && s[z[i]] == s[i + z[i]])
z[i]++; // extend match
if (i + z[i] > r) { // new rightmost box
l = i;
r = i + z[i];
}
}
return z;
}function zArray(s) {
const n = s.length;
const z = new Array(n).fill(0);
let l = 0,
r = 0; // current z-box
for (let i = 1; i < n; i++) {
if (i < r) z[i] = Math.min(r - i, z[i - l]); // reuse
while (i + z[i] < n && s[z[i]] === s[i + z[i]])
z[i]++; // extend
if (i + z[i] > r) {
// new rightmost box
l = i;
r = i + z[i];
}
}
return z;
}Inside a known match? Copy
z[i-l]. Then extend. Then claim the box if you reached further.
Pattern 2: Pattern Search with a Separator
Concatenate pattern + '#' + text; any Z[i] == m in the text part is a full match:
public List<Integer> search(String text, String pat) {
String combined = pat + "#" + text;
int m = pat.length();
int[] z = zArray(combined);
List<Integer> hits = new ArrayList<>();
for (int i = m + 1; i < combined.length(); i++) {
if (z[i] == m) {
hits.add(i - m - 1); // index in text
}
}
return hits;
}def search(text, pat):
combined = pat + "#" + text
m = len(pat)
z = z_array(combined)
return [i - m - 1
for i in range(m + 1, len(combined))
if z[i] == m]vector<int> search(const string& text, const string& pat) {
string combined = pat + "#" + text;
int m = pat.size();
vector<int> z = zArray(combined);
vector<int> hits;
for (int i = m + 1; i < (int)combined.size(); i++)
if (z[i] == m)
hits.push_back(i - m - 1);
return hits;
}function search(text, pat) {
const combined = pat + "#" + text;
const m = pat.length;
const z = zArray(combined);
const hits = [];
for (let i = m + 1; i < combined.length; i++)
if (z[i] === m) hits.push(i - m - 1);
return hits;
}The separator must not appear in either input —
'#'is safe for lowercase text.
Pattern 3: Longest Prefix That Is Also a Suffix
Scan the Z-array of s itself — the largest Z[i] where i + Z[i] == n:
public String longestPrefixSuffix(String s) {
int n = s.length();
int[] z = zArray(s);
int bestLen = 0, start = -1;
for (int i = 1; i < n; i++) {
if (z[i] > bestLen && i + z[i] == n) {
bestLen = z[i]; // reaches the end → suffix
start = i;
}
}
return start == -1 ? "" : s.substring(start);
}def longest_prefix_suffix(s):
n = len(s)
z = z_array(s)
best_len, start = 0, -1
for i in range(1, n):
if z[i] > best_len and i + z[i] == n:
best_len, start = z[i], i
return "" if start == -1 else s[start:]string longestPrefixSuffix(const string& s) {
int n = s.size();
vector<int> z = zArray(s);
int bestLen = 0, start = -1;
for (int i = 1; i < n; i++)
if (z[i] > bestLen && i + z[i] == n) {
bestLen = z[i]; // reaches the end → suffix
start = i;
}
return start == -1 ? "" : s.substr(start);
}function longestPrefixSuffix(s) {
const n = s.length;
const z = zArray(s);
let bestLen = 0,
start = -1;
for (let i = 1; i < n; i++)
if (z[i] > bestLen && i + z[i] === n) {
bestLen = z[i]; // reaches the end → suffix
start = i;
}
return start === -1 ? "" : s.slice(start);
}Common Mistakes
Using Z[0].
By convention it’s undefined/n — loops must start at i = 1.
Wrong seed inside the box.
min(r − i, z[i − l]), not just z[i − l] — the box may end before the copied match does.
Separator that appears in the input.
'#' breaks on strings containing '#' — pick any character guaranteed absent.
Complexity
| Step | Time | Space |
|---|---|---|
| Build Z-array | O(n) | O(n) |
| Search via separator | O(n + m) | O(n + m) |
Premium Content
Unlock Z Algorithm and all premium lessons with a subscription.
From ₹199.99/year — See plans