Z String Search
The Z-algorithm finds occurrences of a “word” $W$ within a main “text string” $T$ in linear time $O(|W| + |T|)$.
Given a string $S$ of length $n$, the algorithm produces an array, $Z$ where $Z[i]$ represents the longest substring starting from $S[i]$ which is also a prefix of $S$. Finding $Z$ for the string obtained by concatenating the word, $W$ with a nonce character, say $$$ followed by the text, $T$, helps with pattern matching, for if there is some index $i$ such that $Z[i]$ equals the pattern length, then the pattern must be present at that point.
While the $Z$ array can be computed with two nested loops in $O(|W| * |T|)$ time, the following strategy shows how to obtain it in linear time, based on the idea that as we iterate over the letters in the string (index $i$ from $1$ to $n - 1$), we maintain an interval $[L, R]$ which is the interval with maximum $R$ such that $1 ≤ L ≤ i ≤ R$ and $S[L…R]$ is a prefix that is also a substring (if no such interval exists, just let $L = R = - 1$). For $i = 1$, we can simply compute $L$ and $R$ by comparing $S[0…]$ to $S[1…]$.
Example of Z array
1
2
3
Index 0 1 2 3 4 5 6 7 8 9 10 11
Text a a b c a a b x a a a z
Z values X 1 0 0 3 1 0 0 2 2 1 0
Other examples
1
2
str = a a a a a a
Z[] = x 5 4 3 2 1
1
2
str = a a b a a c d
Z[] = x 1 0 2 1 0 0
1
2
str = a b a b a b a b
Z[] = x 0 6 0 4 0 2 0
Example of Z box

Complexity
-
Time: $O( W + T )$ -
Space: $O( W )$
Implementations
-
1
# TODO: Implement in Python -
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
// let pattern = "aab"; // let text = "aabxaabxcaabxaabxay"; const pattern = 'abc'; const text = 'xabcabzabc'; const len = pattern.length + text.length + 1; const z = new Array(len); z[0] = 0; function createZarr(concat) { let left; let right; let N; N = concat.length; left = 0; right = 0; for (let i = 1; i < N; i++) { if (i > right) { left = right = i; while (right < N && concat[right] === concat[right - left]) { right++; } z[i] = (right - left); console.log('--------------------------------'); console.log(`Value of z[${i}] = the length of the substring starting from ${i} which is also the prefix of the concatinated string(=${right - left})`); console.log('--------------------------------'); right--; } else if (z[i - left] < (right - i + 1)) { z[i] = z[i - left]; } else { console.log(`The substring from index ${i - left} will cross the right end.`); left = i; while (right < N && concat[right] === concat[right - left]) { right++; } z[i] = (right - left); right--; console.log('--------------------------------'); console.log(`Value of z[${i}] = the length of the substring starting from ${i} which is also the prefix of the concatinated string(=${right - left})`); console.log('--------------------------------'); } } } const concat = `${pattern}$${text}`; const patLen = pattern.length; createZarr(concat); console.log('The Values in Z array equal to the length of the pattern indicates the index at which the pattern is present'); console.log('==================================='); for (let i = 0; i < len; i++) { if (z[i] === patLen) { const pos = i - (patLen + 1); console.log(`Pattern Found at index ${pos}`); } } console.log('===================================');