Convention: string indices start at .
Definition
For a string of length , define as the length of the longest common prefix (LCP) of and , the suffix beginning at . Then is called the Z function of , and .
The algorithm for computing this array is generally called the Z algorithm internationally, while in China it is called extended KMP (exKMP).
This article introduces an -time algorithm for computing the Z function and its applications.
Explanation
The following examples show the Z function for different strings:
Naive algorithm
The naive Z-function algorithm has complexity :
Implementation
C++
vector<int> z_function_trivial(string s) {
int n = (int)s.length();
vector<int> z(n);
for (int i = 1; i < n; ++i)
while (i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i];
return z;
}
Python
def z_function_trivial(s):
n = len(s)
z = [0] * n
for i in range(1, n):
while i + z[i] < n and s[z[i]] == s[i + z[i]]:
z[i] += 1
return z
Linear algorithm
As in many string algorithms, the key is to use the automaton idea to find a state-transition function under the relevant constraints, so that previously computed states can accelerate new computations.
In this algorithm, we compute sequentially from to (). When computing , we use the already computed values .
For , we call the interval the matching segment of , also known as a Z-box.
During the algorithm, we maintain the matching segment with the rightmost right endpoint. Denote it by . By definition, is a prefix of . When computing , we maintain . Initially, .
When computing :
- If , then by the definition of , , so . Then:
- If , set .
- Otherwise . Set , then compare subsequent characters directly to extend as far as possible.
- If , compute directly by comparing from , as in the naive algorithm.
- After computing , if , update by setting and .
Visit this website to see a simulation of the Z function.
Implementation
C++
vector<int> z_function(string s) {
int n = (int)s.length();
vector<int> z(n);
for (int i = 1, l = 0, r = 0; i < n; ++i) {
if (i <= r && z[i - l] < r - i + 1) {
z[i] = z[i - l];
} else {
z[i] = max(0, r - i + 1);
while (i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i];
}
if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
}
return z;
}
Python
def z_function(s):
n = len(s)
z = [0] * n
l, r = 0, 0
for i in range(1, n):
if i <= r and z[i - l] < r - i + 1:
z[i] = z[i - l]
else:
z[i] = max(0, r - i + 1)
while i + z[i] < n and s[z[i]] == s[i + z[i]]:
z[i] += 1
if i + z[i] - 1 > r:
l = i
r = i + z[i] - 1
return z
Complexity analysis
Each execution of the inner while loop moves forward by at least one position. Since , it executes at most times in total.
The outer loop makes only one linear pass.
The total complexity is .
Applications
We now consider applications of the Z function in several concrete settings.
These applications are largely similar to those of the prefix function.
Matching all occurrences
To avoid confusion, call the text and the pattern. The problem is to find every occurrence of pattern in text .
To solve this problem, construct : concatenate and with a separator between them, chosen so that it occurs in neither nor .
First compute the Z function of . For any in , consider the Z value in for the suffix beginning at . If , an occurrence of starts at position in ; otherwise no occurrence starts there.
Its time complexity, which is also its space complexity, is .
Number of distinct substrings
Given a string of length , compute the number of distinct substrings of .
Consider the increment: knowing the current number of distinct substrings of , compute the number after appending one character to the end of .
Let be the current number of distinct substrings of . Append a new character to the end of . Clearly, some new substrings ending in appear; these are substrings ending in that did not occur before.
Let be the reverse of . We need to count how many prefixes of do not occur elsewhere in . Compute the Z function of and let its maximum be . The reverses of prefixes of with length at most are substrings ending in that already occurred in .
Therefore, the number of new substrings after appending to is .
The algorithm has time complexity .
Notably, the same method can recompute the number of distinct substrings in time after adding a character at an endpoint or deleting one from either the end or the beginning.
String periods
Given a string of length , find its shortest full period: the shortest string such that can be represented as a concatenation of copies of .
Compute the Z function of . The length of its full period is the smallest divisor of satisfying .
The proof is the same as the proof for the corresponding application of the prefix function.
Practice problems
- Luogu P5410 (https://www.luogu.com.cn/problem/P5410) extended KMP/exKMP (Z function)
- Luogu P7114 (https://www.luogu.com.cn/problem/P7114) string matching
- CF126B Password
- UVa # 455 Periodic Strings
- UVa # 11022 String Factoring
- UVa 11475 - Extend to Palindrome
- Codechef - Chef and Strings
- Codeforces - Prefixes and Suffixes
- Leetcode 2223 - Sum of Scores of Built Strings
This page is mainly translated from Z-function of a string and its computation and its English translation Z-function and its calculation. The Russian version is licensed under Public Domain + Leave a Link; the English version is licensed under CC-BY-SA 4.0.