kmp, 字符串相同前后缀
catalog
- 相同前后缀
- O(n)构造 最长前后缀 数组
- 代码
- 在kmp中的用处、kmp原理
- 暴力
- 优化A_ind
- 优化B_ind
- kmp模板
相同前后缀
kmp的本质,就是 相同前后缀
所谓 相同前后缀:a b c d e f g h前缀prefix: [a, b, ...] 后缀suffix: [..., g, h]比如最长相同前后缀的长度为3即表示: [a, b, c] == [f, g, h]
且规定: 最长相同前后缀的长度 != n,否则无意义
因为,对于任意一个串s: 长度为n的前缀s == 长度为n的后缀s
所以, 我们这里讨论的 一个串的“最长前后缀长度”,其实严格说是“次长”,即必须长度 < n
一个长度为n的串,他最长前后缀长度 所有可能是: [0, n-1]
长度为偶数6的串 | 最长前后缀长度 | 长度为奇数5的串 | 最长前后缀长度 |
---|---|---|---|
aaaaaa | 5(aaaaa) | aaaaa | 4(aaaa) |
ababab | 4(abab) | ababa | 3(aba) |
abcabc | 3(abc) | abcde | 0() |
如果s中,没有与s[1]相同的字符,那么s的最长前后缀肯定为0,因为前后缀要匹配,至少第一个字符要相同
O(n)构造 最长前后缀 数组
已知s串[1,2,3,..n]的前后缀长度为k,求[1,2,3,..,n,n+1]串的前后缀长度 '简称len为 最长前后缀长度 's: 1, 2, 3, 4, 5, 6, ..., n
s[1,n]的前缀: [1 2 3 ... k-2 k-1 k]
s[1,n]的后缀: [. . . ... n-2 n-1 n] ' 这俩是 对应上下位相同的 '当此时新来了一个 [n+1]元素时,找s[1, n+1]的最长前后缀,即:
s[1, n+1]的前缀pre: [1 2 3 ... j-1 j ]
s[1, n+1]的后缀suf: [. . . ... n n+1]目的是: 使pre==suf,且长度最长
由于,前缀pre的头[1] 和 后缀的尾[n+1] 是固定死的, 这是前后缀的要求
即,因为suf[n+1]是固定死的,让suf最长,即让[...n](suf去除[n+1]后的部分)最长' 即找最长的: pre[1 2 3 ... j-1] 和 suf[... n-1 n] '可以发现,他的答案,就是 s[1,n]的len但需要有个前提: pre[j] == suf[n+1] ' j就是 s[1,n]的len '即if( s[ (s[1,n]的len) + 1 ] == s[ n+1 ] )s[1,n+1]的len = s[1,n]的len + 1' 即,新加入1个元素[n+1], 整个串的len 最大是 原来串的len+1 '关键是当else的情况,由于,s[j] != s[n+1],而后缀suf的[n+1]是固定死的只能让j往左走,假设j往左走到了i(是答案)
s[1,n+1]的前缀pre: [1 2 3 ... i-1 i ]
s[1,n+1]的后缀suf: [. . . ... n n+1]他需要满足2点:1, pre[1 2 3 .. i-1] == suf[. . . . n]2, pre[i] == suf[n+1]其中第1点,就是s[1, n]的相同前后缀问题(但不是最长,但他要尽量的长)for( sub : s[1,n]的相同前后缀 ){sub从长到短,sub表示s[1,..,i]和s[...,n]是相同前后缀if( s[i + 1] == s[n + 1] ){s[1, n+1]的len = i;break; }}这就是伪代码。即问题变为: 如何遍历s[1,n]串的 len (且从大到小)先看最大的len: 等于i:
s[1,n]的前缀pre1: [1 2 3 ... i-1 i ]
s[1,n]的后缀suf1: [. . . ... n-1 n ]如何从最大的len, 得到 次大的len呢???
令次大的len为: j(j < i)
s[1,n]的前缀pre2: [1 2 ... j-1 j]
s[1,n]的后缀suf2: [. . ... n-1 n]suf2是suf1的后缀,因为suf1==pre1,所以,' suf2 是 pre1的后缀!!'即,pre2==suf2,就变成了: pre1的前缀 == pre1的后缀即,找pre1的 len (i == pre1的len)int i = s[1,n]的len;
while(i != 1){i = s[1, i]的len;
}
代码很短,但这是 非常非常重要的!! 一定要理解。
代码
void build_len(const string& s, int *len){ // s下标从0开始len[0] = 0;int n = s.size();FOR(i, 1, n-1, 1){int pre_len = len[i - 1];while(true){if(pre_len == 0){ if(s[i] == s[0]){ len[i] = 1; }else{ len[i] = 0; }break; }if(s[i] == s[pre_len]){ len[i] = pre_len + 1;break; }pre_len = len[pre_len - 1];}}
}
在kmp中的用处、kmp原理
最长前后缀,最大的用处,基于以下的这个性质:
长度为n=12的s串: a , b , c , d , a , b , c , d , a , b , c , d 长度前后缀长度为8(abcdabcd)
我们将这个s串,向后移动,“找第一个交叉区域相同的 位置”
最初s串 | a | b | c | d | a | b | c | d | a | b | c | d | |||||
向后移动1 | a | b | c | d | a | b | c | d | a | b | c | d | 交叉区域长度为11,不相同 | ||||
向后移动2 | a | b | c | d | a | b | c | d | a | b | c | d | 交叉区域长度为10,不相同 | ||||
向后移动3 | a | b | c | d | a | b | c | d | a | b | c | d | 交叉区域长度为9,不相同 | ||||
向后移动4(成功) | a | b | c | d | a | b | c | d | a | b | c | d | 交叉区域长度为8,相同 |
我们发现,当向后移动4后,交叉区域为n-4 = 8,此时完全相同了!!
即: 最少后移X步,使得 交互区域相同。 公式为: X = n - 最长前后缀长度
这当然很容易证明,很容易看出,这就是 “相同前后缀” 的定义
关键是,这个性质 如何应用到 kmp里,这是重点!!!
暴力
首先,回顾最暴力的kmp思路:
A串长度n,B串长度m(m < n)
FOR(A_ind, 0, n-1, 1){bool succ = true;FOR(B_ind, 0, m-1, 1){if(A[A_ind + B_ind] != B[B_ind]){ succ = false; break; }}if(succ){ PD(i); SP; }
}4 5 6 7 8 9 10 11
A: . . . . a b a b a b a c d e . . .
B: a b a b a c d e (B串长度是8)0 1 2 3 4 5 6 7此时A_ind=4, 当前匹配失败后(A[9]!=B[5]),你不可以 直接让A_ind跳到10去因为,A_ind=6时, A[6...] 与 B[0...] 是匹配成功的所以,暴力的算法,你的A_ind 只能1个1个的往后走,即B_ind每走一步,A串从头开始 与B进行匹配
优化A_ind
从上面可以看出,其实我们的A_ind 当前是4,其实可以直接跳到6去
因为上一次是在A[9] != B[5],说明: A[4,…] = B[0,1,2,3,4]
当A[4,5,…] = B[0,1,2,3,4]时,此时你让B[0]和A[5]匹配 也是错的。
你回顾之前的那个性质,令新的字符串str = A[4,5,…] = B[0,1,2,3,4]
str最少往后移动:(str.size() - str的len)步, 才可以匹配
所以,此时直接让: A_ind += (5 - B的len[4] = 2),即A_ind = 6
即让: A[6, …] 与 B[0,…]匹配, 这样就优化了
int A_ind = 0; ' AB串都是从0开始的 '
while(A_ind <= n-m ){int B_ind = 0;while(B_ind < m){if(A[A_ind + B_ind] == B[B_ind]){ ++ B_ind; }else{ break; }}if(B_ind == m){ PD(A_ind); ED;}if(B_ind == 0){A_ind ++; }else{int move = B_ind - len[B_ind - 1];A_ind += move;}'在B[B_ind]匹配失败了,说明: B前B_ind个元素[0,...B_ind] 和 A[A_ind,..] 匹配成功了'
' len[B_ind-1]是:B的前B_ind个元素的 len。 '
注意,千万不要写成: move = (m - len[B_ind - 1])!!!
我们是把, 已经匹配成功的B[0,1,...,B_ind-1],当成了一个新的串!!! 和B没有关系}
优化B_ind
上面的代码,还是超时的。
回顾上面的思路:
此时: A_ind=4 (B_ind=[0,1,2,..] 到5时,出问题)4 5 6 7 8 9 10 11
A: . . . . a b a b a [b] a c d e . . .
B: a b a b a [c] d e (B串长度是8)0 1 2 3 4 5 6 7之前的优化完:4 5 6 7 8 9 10 11
A: . . . . a b a b a [b] a c d e . . .
B: a b a [b] a c d e0 1 2 3 4 5
优化后,A_ind虽然是跳到了6,但是B_ind重新从0开始
但是,其实A[6,7,8] 和 B[0,1,2] 是已经匹配了的,因为我们让A_ind移动的目的 就是这个效果
之前是A_ind=4,在A[9]失败,说明A[4,5,6,7,8]是成功的
这个串A[4,5,6,7,8] 他的前缀==后缀, 前缀是从[4]开始的,后缀是从[k]开始
我们的A_ind,下一次 就是要移动到[k]位置
当A_ind移动到[k]位置, 串A[4,5,6,7,8]的后缀,即[k, k+1, …8] 他就自动是和 B[0,1,2,3…] 是匹配的!!
令l 为 A[4,5,6,7,8]的len
则,在你将A_ind移动完后(将A_ind += (5 - len))
此时: A[A_ind, …, A_lnd+l-1] 自动= B[0,1,…, l-1]
所以,你的B_ind 直接设置为: [l]即可。
从B[l]开始,和A[A_ind+l],开始匹配
即,此时的B_ind 不再是,每次从0开始。
他变成了一个全局量
int A_ind = 0, B_ind = 0;
while(A_ind <= n - m){while(B_ind < m){if(A[A_ind + B_ind] == B[B_ind]){ ++ B_ind; }else{ break; }}if(B_ind == m){ PD(A_ind); SP;}
// A[A_ind,...] 与 B[0,1,2,3..., B_ind-1] 是完全匹配的if(B_ind == 0){A_ind ++; }else{int _len = len[B_ind - 1]; ' B[0,1,2...,B_ind-1]的len 'A_ind += (B_ind - _len); B_ind = _len; ' 等价于: B_ind -= (B_ind - _len) '' 将A_ind += (xx)后, 此时A[A_ind,..]与B[0,1,..,_len-1]是已经匹配了的。 可以直接让B_ind从[_len]开始 '}
}
kmp模板
A串长度为n,B串长度为m(n >= m) 均从0开始
void build_len(const string& s, int *len){len[0] = 0;int n = s.size();FOR(i, 1, n-1, 1){int pre_len = len[i - 1];while(true){if(pre_len == 0){ if(s[i] == s[0]){ len[i] = 1; }else{ len[i] = 0; }break; }if(s[i] == s[pre_len]){ len[i] = pre_len + 1;break; }pre_len = len[pre_len - 1];}}
}build_len(B, len);int A_ind = 0, B_ind = 0;
while(A_ind <= n - m){while(B_ind < m){if(A[A_ind + B_ind] == B[B_ind]){ ++ B_ind; }else{ break; }}if(B_ind == m){ PD(A_ind); SP;}if(B_ind == 0){A_ind ++; }else{int _len = len[B_ind - 1];A_ind += (B_ind - _len); B_ind = _len;}
}
kmp, 字符串相同前后缀相关推荐
- HDU 4763 Theme Section(KMP+枚举公共前后缀)
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=4763 题目大意: 给你一个字符串s,存在一个 子串E同时出现在前缀.中间.后缀,即EAEBE这种模式 ...
- `算法知识` 字符串相同前后缀
相同前后缀 对于长度N的字符串, 找到其长度最长的相同前后缀的长度 (且长度需<N) 字符串ababab的最长前后缀为abab; 字符串abcdabc的最长前后缀为abc 这个概念不应该陌生, ...
- 字符串——OKR-Periods of Words(kmp求最短相同前后缀或者说求最长循环节)
传送门:OKR-Periods of Words 思路:题目要求的是最大的前缀Q使得A是QQ的前缀,同时Q不能等于A, 比如在bababab,要使得周期最大,应该选的循环节就是bababa, 另一个有 ...
- kmp算法中字符串前后缀公共长度的总结
kmp算法比较重要的一步骤,需要根据模式串构建next或nextval数组 通过前后缀公共字符串最大长度来进行构建的,前后缀概念是分成2个的,字符串前缀和字符串后缀 字符串前缀的意思是,不包含字符串最 ...
- KMP算法及next数组(最大公共前后缀)求解
KMP算法及next数组(最大公共前后缀)求解 2020.12.14理解: 1. KMP算法 网上关于KMP算法讲解较为简单易懂,因此在此只作简述: 在字符串s中匹配字符串t: S: ABE-AB-A ...
- Go 学习笔记(51)— Go 标准库之 strings(字符串比较、字符串前后缀、字符串统计、字符串索引、字符串包含、字符串转换、字符串复制、字符串替换、字符串去除、字符串分割和连接)
1. 概述说明 import "strings" strings 包实现了用于操作字符的简单函数. strings 包与 bytes 包中的函数用法基本一样. 2. 主要函数 2. ...
- 最长公共前后缀(KMP中next数组求法)
字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串:后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串.例如对于字符串 abacaba,其前缀有 a, ab, aba, aba ...
- 字符串魔法hard(前后缀与贪心)
字符串魔法hard(前后缀与贪心) 链接:https://ac.nowcoder.com/acm/contest/9680/C 来源:牛客网 description: 白浅获得了一个仅由A和B组成的字 ...
- Kmp算法之 求最大公共前后缀
先抛问题1:如果我们已知一个字符str的最大公共前后缀长度,当这个str再添加一个字符的str2,如何判断这个新的str2的最大公共前后缀长度?? 结论:只要将新添加的字符,与str的最大公共前后缀中 ...
最新文章
- [面试]future模式
- Spring核心AOP(面向切面编程)
- 装饰者模式 php,PHP设计模式之装饰器模式
- 新浪新闻改版了,是到了告别的时候了吗?
- 浏览器崩溃时提示137错误
- Linux_NFS/Samba服务器
- STM32---SPI通信的总结(库函数操作)
- mysql 性能分析 命令_MySQL中使用SHOW PROFILE命令分析性能的用法整理
- 如何用ant将JSP项目打成war包
- Build 2018大会:.NET概述和路线图
- 被骂垃圾货,却卖出8000万副,干翻国外大牌!这个产品杀手凭什么?
- php 取整十整百,php取整数的方法与实例总结
- uva 1291 - Dance Dance Revolution ( dp )
- linux删除用户删不了怎么办,Linux下完全删除用户的两种方法
- java课题设计实验报告,JAVA简单记事本程序设计实验报告
- 大学生学科竞赛管理系统/竞赛管理系统的设计与实现
- 中文编码之GB2312,Big5,GBK简介
- MATLAB全局变量
- 开关磁阻电机控制仿真 开关磁阻电机传统控制:电流斩波控制、电压PWM控制、角度位置控制。 智能控制:12/8三相开关磁阻电机有限元分析本体建模
- 如何批量修改文件名、照片文件名