Z Function (Extended KMP)

Convention: string indices start at 00.

Definition

For a string nn of length ss, define z[i]z[i] as the length of the longest common prefix (LCP) of ss and s[i,n−1]s[i,n-1], the suffix beginning at s[i]s[i]. Then zz is called the Z function of ss, and z[0]=0z[0] = 0.

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 O(n)O(n)-time algorithm for computing the Z function and its applications.

Explanation

The following examples show the Z function for different strings:

  • z(aaaaa)=[0,4,3,2,1]z(\mathtt{aaaaa}) = [0, 4, 3, 2, 1]
  • z(aaabaab)=[0,2,1,0,2,1,0]z(\mathtt{aaabaab}) = [0, 2, 1, 0, 2, 1, 0]
  • z(abacaba)=[0,0,1,0,3,0,1]z(\mathtt{abacaba}) = [0, 0, 1, 0, 3, 0, 1]

Naive algorithm

The naive Z-function algorithm has complexity O(n2)O(n^2):

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 11 sequentially from n−1n-1 to z[i]z[i] (z[0]=0z[0]=0). When computing z[i]z[i], we use the already computed values z[0],…,z[i−1]z[0],\ldots,z[i-1].

For ii, we call the interval [i,i+z[i]−1][i,i+z[i]-1] the matching segment of ii, also known as a Z-box.

During the algorithm, we maintain the matching segment with the rightmost right endpoint. Denote it by [l,r][l,r]. By definition, s[l,r]s[l,r] is a prefix of ss. When computing z[i]z[i], we maintain l≤il\le i. Initially, l=r=0l=r=0.

When computing z[i]z[i]:

  • If i≤ri\le r [l,r][l,r] s[i,r]=s[i−l,r−l]s[i,r] = s[i-l,r-l] z[i]≥min⁡(z[i−l],r−i+1)z[i]\ge \min(z[i-l],r-i+1) z[i−l]<r−i+1z[i-l] < r-i+1 z[i]=z[i−l]z[i] = z[i-l] z[i−l]≥r−i+1z[i-l]\ge r-i+1 z[i]=r−i+1z[i] = r-i+1 z[i]z[i] i>ri>r s[i]s[i] z[i]z[i] z[i]z[i] i+z[i]−1>ri+z[i]-1>r [l,r][l,r] l=i,r=i+z[i]−1l=i, r=i+z[i]-1, 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 rr 11 r<n−1r< n-1 nn 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 O(n)O(n).

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 tt the text and pp the pattern. The problem is to find every occurrence of pattern tt in text pp.

To solve this problem, construct s=p+⋄+ts = p + \diamond + t pp tt ⋄\diamond ⋄\diamond pp tt: concatenate and with a separator between them, chosen so that it occurs in neither nor .

First compute the Z function of ss [0,∣t∣−1][0,|t| - 1] ii t[i]t[i] ss k=z[i+∣p∣+1]k = z[i + |p| + 1] k=∣p∣k = |p| pp tt ii pp tt ii. 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 O(∣t∣+∣p∣)O(|t| + |p|).

Number of distinct substrings

Given a string nn of length ss, compute the number of distinct substrings of ss.

Consider the increment: knowing the current number of distinct substrings of ss, compute the number after appending one character to the end of ss.

Let kk be the current number of distinct substrings of ss. Append a new character cc to the end of ss. Clearly, some new substrings ending in cc appear; these are substrings ending in cc that did not occur before.

Let tt be the reverse of s+cs + c. We need to count how many prefixes of tt do not occur elsewhere in tt. Compute the Z function of tt and let its maximum be zmax⁡z_{\max}. The reverses of prefixes of tt with length at most zmax⁡z_{\max} are substrings ending in ss that already occurred in cc.

Therefore, the number of new substrings after appending cc to ss is ∣t∣−zmax⁡|t| - z_{\max}.

The algorithm has time complexity O(n2)O(n^2).

Notably, the same method can recompute the number of distinct substrings in O(n)O(n) time after adding a character at an endpoint or deleting one from either the end or the beginning.

String periods

Given a string nn of length ss, find its shortest full period: the shortest string tt such that ss can be represented as a concatenation of copies of tt.

Compute the Z function of ss. The length of its full period is the smallest divisor nn of ii satisfying i+z[i]=ni+z[i]=n.

The proof is the same as the proof for the corresponding application of the prefix function.

Practice problems

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.

This content comes from OI Wiki: Z Function (Extended KMP). See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.