title: “Z函数”
date: 2024-10-24 11:38:41
updated: 2024-12-27 08:25:31
tag: [“notion”, “Algorithm”, “数组字符串”]
categories: “Algorithm”
mathjax: true
comments: true
description: ‘


Z函数

对于一个长度为 $n$ 的字符串 $s$,定义函数 $z[i]$ 表示 $s$ 和 $s[i,n-1]$(即以 $s[i]$ 开头的后缀)的最长公共前缀(LCP)的长度,则 $z$ 被称为 $s$ 的 Z 函数。特别地,$z[0] = 0$。维护右端点最靠右的匹配段,记作$[l,r]$。

在计算$z[i]$的过程中:

data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

vector<int> z_func(const string& s) {
    int n = 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;
}

注意到在while (i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i]; 之前z[i] 达到了$r$,所以每次++z[i] 才会增长$r$。$r=l+z[i]-1$,只用$l$一个变量就可以得到$r$,一旦触发循环则只需要更新一次$l$

vector<int> z_func(const string& s) {
    int n = s.size();
    vector<int> z(n);
    for (int i=1,l=0; i<n; i++) { // r = l+z[l]-1; 最右匹配点
        if (i<l+z[l]) z[i] = min(z[i-l], l+z[l]-i);
        while (i+z[i]<n && s[z[i]] == s[i+z[i]]) ++z[l=i];
    }
    return z;
}