基础数据结构之字符串

字符串的两种经典算法:KMP算法和最小表示法。

KMP算法

《算法竞赛进阶指南》上的推导过程写的很详细,推荐看一下。

推导过程的难点在于要理解如何遍历某一位置i的“候选值”集合。理解之后其它问题就都迎刃而解了。


一.循环节问题。 我们假设某一字符串的子串经过连续复制后可以覆盖原字符串,那么就称该子串为循环节;也就是说,这里的循环节经过连续复制后的字符串相比于原字符串可以“多余”。而该问题的循环节要求不可以“多余”。这里有一个结论:对于长度为n的字符串,最小循环节的长度是n - next[n]; 如果n能整除 n - next[n],那么此循环节不“多余”,并且其它循环节都是最小循环节的倍数。

我们来证明一下n - next[n]为什么是循环节。
后缀[c, d]等于前缀[a, b]。将前缀[a, b]平移,直到与[c, d]重合得到[a’, b’]。[a, c]就是n - next[n]。
在这里插入图片描述
将c’向线段[a, d]做投影,投影点为e。因为[a’, b’]等于[a, b],所以肯定存在一个对应的点e‘。[a’, c’]等于[a, c],而[a’, c’]又等于[c, e],所以[a, c]等于[c, e],所以[a’, c’]等于[c’, e’]。

在这里插入图片描述
重复此操作,重复操作就是[a, c]的循环过程:

在这里插入图片描述
如果f’和b重合,很明显[a, c]就是循环节。一旦f’不和b重合,那么"[a, c]就是“多余”的循环节。我们来看一下为什么“多余”:[f, b]是[f, h]的前缀,而[f, b]就是[f’,b’];又因为[a’. b’]与[c, d]重合,所以[h, d]等于[f’, b’],所以[h, d]也是[f, h]的前缀。而[f, h]等于[a, c],因此,[a, c]经过无限复制后,肯定能覆盖[a, d],也就能证明出[a, c]就是循环节。
在这里插入图片描述

那么[a, c]是不是最小循环节的呢?假设存在一个更小的循环节,由上面的证明过程可知,肯定存在有比next[n]更长的匹配长度,这与next[n]的定义矛盾。

证毕。

周期

对于该题我这里还有一个疑问:假设n不能整除n - next[n],那么是否存在长度比n - next[n]更大的不“多余”的循环节呢?恳请看到这个问题的大佬回答。

奶牛矩阵(二维应用)

根据题目描述,最小覆盖子矩阵可以看作二维空间上的循环节,我们首先联想到循环节问题。

在一维空间上,最小循环节肯定可以对齐左端。扩展到二维,最小覆盖子矩阵也肯定可以对齐左上角:假设有一个不对齐左上角的最小覆盖子矩阵。在理解了一维的基础上,对二维的x、y方向单独找循环节,就可以发现存在一个对齐左上角、面积同样最小的覆盖子矩阵。

我们可以联想到一个很模糊的做法:每一行Ri都有一个循环节长度集合Si,设width为all Si的交集中最小的元素。列方向也有一个最小元素height。那么S = width * height即为答案。

那么具体应该怎么做呢?数据范围中,1≤C≤75,列数很少。也就是说,每一行的循环节长度len满足1 <= len <= 75。我们不妨从第一行开始枚举,观察在前i - 1行可以满足的循环节长度中,第i行有哪些不能满足。定义一个bool visit[80]就可以实现嘛。最后我们就得到Si的交集。

有了交集,我们可以很轻松的找到width。因为行数较大,所以我们不能用同样的方法求height。我们不妨把每一行长度为width的一段看成一个字母,之后再用kmp算法求出height。

这里有一个问题:是否存在长度大于width的循环节,其得到的height更小,使得整个面积更小呢?是不可能的。我们取两行,假设它们长度为width的那一段相等,那么长度大于width的一段就可能不相等;假设它们长度为width的那一段不相等,那么长度大于widht的一段肯定不相等。看一个具体的例子:我们在列方向上随便取两个相邻的循环节:a b c d a b c d(每个字母表示长度为width的字符串),如果我们取长度大于widht的字符串,用kmp算法再求一遍height,那么肯定满足:

  1. 不同的字母之间还是不同。
  2. 新得到的字母不可能是a b c d
  3. 相同的字母之间可能变得不相同

也就是说height只可能更大。证毕。

二 KMP模式匹配

匹配统计

参考了大佬的思路:

https://www.acwing.com/solution/content/7792/

观察到该题属于“观察两段字符串是否相等”的问题,多半有二分+hash的做法。二分+hash的方法很简单,不多赘述。

kmp做法比较难。如何想到kmp做法的呢?回忆一下kmp算法的过程:先对B进行“自我匹配”,再让A匹配B。如果我们遍历到字符串A的A[j]点,那么匹配长度len的含义是:以A[j]结尾的后缀与B的前缀能匹配的最大长度为len。我们可以通过next[len]、next[next[len]]……得到所有的匹配长度。那么从j - len + 1开始的后缀子串长度一定不小于len,从j - next[len] + 1开始的后缀子串长度一定不小于next[len]……

这里要用到后缀和思想:sum[i]表示匹配长度不小于i的后缀子串数量。那么

s = sum[i] - sum[i + 1]

s即为数量等于i的子串个数。

我们怎么用kmp算法得到sum数组呢?为了方便理解,我们先定义一个新变量num[i, j]:以A[j]结尾的后缀与B的前缀匹配长度为i的子串个数,只可能是0或1嘛。我们观察到:

  1. 如果存在num[i, j],那么从 j - i + 1 开始的后缀子串长度一定不小于i
  2. 对于固定的i和不同的j,它们表示的后缀子串各不相同
  3. 对于所有长度不小于i的后缀子串,一定有唯一对应的num[i, j]

我们再kmp算法的过程中求出num数组,再得到sum数组,就可以得到答案了。这么做要求我们需要在kmp算法的过程中,遍历以A[j]结尾的后缀子串与B的前缀能够匹配的所有长度,会超时。因此我们要想办法进行优化:

  1. 不需要设立num数组。sum数组是由num数组求和得到的,我们在遍历的过程中直接求出sum数组即可,即将num[i, j] ++ 改为 sum[i] ++;
  2. 我们没必要遍历以A[j]结尾的后缀与B的前缀能够匹配的所有长度。对于不同的j,固定的i,它们都有一个num[next[i], j]。我们假设sum数组只保存长度为len的子串(len表示以A[j]结尾的后缀与B的前缀能够匹配的最大长度),那么只有sum[max(len)]符合sum数组原本的定义。我们自顶向下遍历所有len,对每个len都执行sum[next[len]] += sum[len],就能够得到sum数组。

字符串的最小表示法

项链

模板题。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值