Z 函数(扩展 KMP)

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++**实现。

  • 58
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

闻缺陷则喜何志丹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值