Z 函数(扩展 KMP)简介
约定:字符串下标以 0 为起点。
定义对于一个长度为 n 的字符串 s,定义函数 z[i] 表示 s 和 s[i,n-1](即以 s[i] 开头的后缀)的最长公共前缀(LCP)的长度,则 z 被称为 s 的 Z 函数。特别地,z[0] = 0。
国外一般将计算该数组的算法称为 Z Algorithm,而国内则称其为 扩展 KMP。
这篇文章介绍在 O(n) 时间复杂度内计算 Z 函数的算法以及其各种应用。
∀
\forall
∀(left,r), r = left+z[left]-1。如果 i
∈
\in
∈[left,r],则可以通过z[0…i-1]加速。
显然s[0…r-left] 等于 z[left,r]。
令i2 = i - left
∀
\forall
∀i1$in[0,right-i] s[i2+i1] = s[i+i1];
∀
\forall
∀i1$in[0,z[i2]-1] s[i1] = s[i2+i1];
两者联合:
iMin = min(z[i2]-1,right-i)
∀
\forall
∀i1$in[0,iMin ] s[i1] = s[i+i1]; 即:最长公共前缀至少是:s[0…iMin]余下部分,暴力统计。
我们不需要记录所有的(l,r),只需要记录r最大的(l,r)。
封装代码
class KMPEx
{
public:
static vector<int> ZFunction(string s) {
int n = (int)s.length();
vector<int> z(n);
for (int i = 1, left = 0, r = 0; i < n; ++i) {
if (i <= r) {//如果此if,r-i+1可能为负数
z[i] = min(z[i - left], r - i + 1);
}
while ((i + z[i] < n) && (s[z[i]] == s[i + z[i]])) {
z[i]++;
}
if (i + z[i] - 1 > r) left = i, r = i + z[i] - 1;
}
return z;
}
protected:
vector<int> m_vLCP;
};
单元测试
namespace UnitTestKMPEx
{
string s;
TEST_CLASS(UnitTest)
{
public:
TEST_METHOD(TestMethod0)
{
s = "babab";
auto v2 = KMPEx::ZFunction(s);
AssertEx(vector<int>{0, 0, 3, 0, 1}, v2);
}
TEST_METHOD(TestMethod2)
{
s = "azbazbzaz";
auto v1 = KMPEx::ZFunction2(s);
auto v2 = KMPEx::ZFunction(s);
AssertEx(vector<int>{0, 0,0, 3,0,0, 0, 2,0}, v2);
}
TEST_METHOD(TestMethod3)
{
s = "azbxddafgxxsdddffsssssgggbzaz";
auto v2 = KMPEx::ZFunction(s);
AssertEx(vector<int>{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0}, v2);
}
TEST_METHOD(TestMethod4)
{
s = "azb1xdda3ddsfgazbxxsdddddddfazb1xfsaasssabsgggbzaz";
auto v2 = KMPEx::ZFunction(s);
AssertEx(vector<int>{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0}, v2);
}
TEST_METHOD(TestMethod5)
{
s = "aaaaa";
auto v2 = KMPEx::ZFunction(s);
AssertEx(vector<int>{0,4,3,2,1}, v2);
}
};
}
时间复杂度
if (i <= r) {//如果此if,r-i+1可能为负数
z[i] = min(z[i - left], r - i + 1);
}
∀ \forall ∀i 时间复杂度是O(1),故总时间复杂度是O(n)
while ((i + z[i] < n) && (s[z[i]] == s[i + z[i]])) {
z[i]++;
}
如果相等,则一定是大于r,且r会相应增加,故相等的总时间复杂度是O(n)。
$\forall$i ,如果不等,只会执行一次,故不等的总时间复杂度也是O(n)
if (i + z[i] - 1 > r) left = i, r = i + z[i] - 1;
没有循环,显然时间复杂度是O(1),总时间复杂度是O(n)。
故总时间复杂度是O(n)。
题解
力扣:2223
除了sn = n
其它si = z[n-i]
故其和为:
∑
(
z
)
\sum(z)
∑(z)+n
class Solution {
public:
long long sumScores(string s) {
vector<int> z = KMPEx::ZFunction(s);
long long llRet = 0;
for (const auto& n : z) {
llRet += n;
}
return llRet + s.length();
}
};
扩展阅读
视频课程
有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
相关下载
想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653
我想对大家说的话 |
---|
《喜缺全书算法册》以原理、正确性证明、总结为主。 |
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。