KMP算法解决字符串匹配问题
要解决的问题
假设字符串str长度为N,字符串match长度为M,M <= N, 想确定str中是否有某个子串是等于match的。返回和match匹配的字符串的首字母在str的位置,如果不匹配,则返回-1
OJ可参考:LeetCode 28. 实现 strStr()
暴力方法
从str串中每个位置开始匹配match串,时间复杂度O(M*N)
KMP算法
KMP算法可以用O(N)时间复杂度解决上述问题。
流程
我们规定数组中每个位置的一个指标,这个指标定义为
这个位置之前的字符前缀和后缀的匹配长度,不要取得整体。
例如: ababk 这个字符串,k位置的指标为2, 因为k之前位置的字符串为abab
前缀ab 等于 后缀ab,长度为2,下标为3的b的指标为1,因为b之前的字符串aba ,前缀a 等于后缀a, 长度为1。
人为规定:0位置的指标是-1,1位置的指标0
假设match串中每个位置我们都已经求得了这个指标值,放在了一个next数组中,这个数组有助于我们加速整个匹配过程。
我们假设在某个时刻,匹配的到的字符如下

其中str的i..j一直可以匹配上match串的0...m, str中的x位置和match串中的y位置第一次匹配不上。如果使用暴力方法,此时我们需要从str的i+1位置重新开始匹配match串的k位置,而KMP算法,利用next数组,可以加速这一匹配过程,具体流程是,依据上例,我们可以得到y位置的next数组信息,假设y的next数组信息是2,如下图

如果y的next数组信息是2,那么0...k 这一段完全等于f...m这一段,那么对于match来说,当y位置匹配不上x位置以后, 可以直接让x位置匹配y的next数组位置p上的值,如下图

如果匹配上了,则x来到下一个位置,p来到下一个位置继续匹配,如果再次匹配不上,假设p位置的next数组值为0, 则继续用x匹配p的next数组位置0位置上的值,如下图

如果x位置的值依旧不等于0位置的值,则宣告本次匹配失败,str串来到x下一个位置,match串从0位置开始继续匹配。
next数组求解
next数组的求解是KMP算法中最关键的一步,要快速求解next数组,需要做到当我们求i位置的next信息时,能通过i-1的next数组信息加速求得,如下图

当我们求i位置的next信息时,假设j位置的next信息为6,则表示

m...n这一段字符串等于s...t这一段字符,此时可以得出一个结论,如果:
x位置上的字符等于j位置上的字符,那么i位置上的next信息为j位置上的next信息加1,即为7。如果不等,则继续看x位置上的next信息,假设为2,则有:

此时,判断q位置的值是否等于j位置的值,如果相等,那么i位置上的next信息为x位置上的next信息加1,即为3,如果不等,则继续看q位置上的next信息,假设为1,那么有

此时,判断p位置的值是否等于j位置的值,如果相等,那么i位置上的next信息为q位置上的next信息加1,即为2,如果不等,则继续如上逻辑,如果都没有匹配上j位置的值,则i位置的next信息为0。
主流程代码复杂度估计
public class LeetCode_0028_ImplementStrStr {
public static int strStr(String str, String match) {
if (str == null || match == null || match.length() > str.length()) {
return -1;
}
if (match.length() < 1) {
return 0;
}
char[] s = str.toCharArray();
char[] m = match.toCharArray();
int l = m.length;
int[] next = getNextArr(m, l);
int x = 0;
int y = 0;
while (y < s.length && x < l) {
if (s[y] == m[x]) {
y++;
x++;
} else if (x != 0) {
x = next[x];
} else {
y++;
}
}
return x == l ? y - x : -1;
}
// 求解next数组逻辑
private static int[] getNextArr(char[] str, int l) {
if (l == 1) {
return new int[]{-1};
}
int[] next = new int[l];
next[0] = -1;
next[1] = 0;
int i = 2; // 目前在哪个位置上求next数组值
int cn = 0; // 前后缀最长字符的长度,也表示下一个要比的信息位置
while (i < next.length) {
if (str[i - 1] == str[cn]) {
next[i++] = ++cn;
} else if (cn > 0) {
cn = next[cn];
} else {
next[i++] = 0;
}
}
return next;
}
}
next数组的求解流程时间复杂度显然为O(N),现在估计主流程的复杂度,主流程中,x能取得的最大值为str字符串的长度N,定义一个变量x-y,能取得的最大值不可能超过N(即当x = N,y=0时候),在主流程的wile循环中,有三个分支
while (y < s.length && x < l) {
if (s[y] == m[x]) {
y++;
x++;
} else if (x != 0) {
x = next[x];
} else {
y++;
}
}
我们考虑这三个分支对于y和y - x变化范围的影响
| 分支 | y | y - x |
|---|---|---|
| x++; y++ | 推高 | 不变 |
| x = next[x] | 不变 | 推高 |
| y++ | 推高 | 推高 |
如上分析,y和y-x都不可能降低,且三个分支只能中一个,所以,而y和y-x的最大值均为N,所有分支执行总推高的次数不可能超过2N。即得出主流程的复杂度O(N)
KMP算法应用
求一个字符串的旋转词(详见:LeetCode 796)
思路
将这个字符串拼接一下, 比如原始串为:123456,拼接成:123456123456
如果匹配的字符串是这个拼接的字符串的子串,则互为旋转词。
一棵二叉树是否为另外一棵二叉树的子树(详见:LeetCode 572)
思路
先将两棵树分别序列化为数组A和数组B,如果B是A的子串,那么A对应的二叉树中一定有某个子树的结构和B对应的二叉树完全一样。
更多
参考资料
KMP算法解决字符串匹配问题的更多相关文章
- Sunday算法解决字符串匹配问题
概述 提起字符串匹配可能更多人会想到KMP算法,该算法时间复杂度为O(m+n),而且也是我们在学习数据结构过程中最早接触到的比较好的算法.但KMP算法需要在模式字符串有关联的情况下,也即模式字符串前后 ...
- 【KMP算法】字符串匹配
一.问题 给定两个字符串S(原串)和(模式串)T,找出T在S中出现的位置. 二.朴素算法 当S[i] != T[j]时,把T往后移一位,回溯S的位置并重新开始比较. (1) 成功匹配的部分(AB ...
- 【算法】字符串匹配之Z算法
求文本与单模式串匹配,通常会使用KMP算法.后来接触到了Z算法,感觉Z算法也相当精妙.在以前的博文中也有过用Z算法来解决字符串匹配的题目. 下面介绍一下Z算法. 先一句话讲清楚Z算法能求什么东西. 输 ...
- Java实现 蓝桥杯 算法提高 字符串匹配
试题 算法提高 字符串匹配 问题描述 给出一个字符串和多行文字,在这些文字中找到字符串出现的那些行.你的程序还需支持大小写敏感选项:当选项打开时,表示同一个字母的大写和小写看作不同的字符:当选项关闭时 ...
- 【数据结构与算法】字符串匹配(Rabin-Karp 算法和KMP 算法)
Rabin-Karp 算法 概念 用于在 一个字符串 中查找 另外一个字符串 出现的位置. 与暴力法不同,基本原理就是比较字符串的 哈希码 ( HashCode ) , 快速的确定子字符串是否等于被查 ...
- 运用kmp算法解决的一些问题的简单题解
学习kmp算法我最后是看的数据结构书上的一本教材学会的..我认为kmp相对于普通的BF算法就是避免了非常多不必要的匹配.而kmp算法的精髓自然就在于next数组的运用...而next数组简而言之就是存 ...
- Boyer Moore算法(字符串匹配)
上一篇文章,我介绍了KMP算法. 但是,它并不是效率最高的算法,实际采用并不多.各种文本编辑器的"查找"功能(Ctrl+F),大多采用Boyer-Moore算法. Boyer-Mo ...
- C++编程练习(7)----“KMP模式匹配算法“字符串匹配
子串在主串中的定位操作通常称做串的模式匹配. KMP模式匹配算法实现: /* Index_KMP.h头文件 */ #include<string> #include<sstream& ...
- 利用KMP算法解决串的模式匹配问题(c++) -- 数据结构
题目: 7-1 串的模式匹配 (30 分) 给定一个主串S(长度<=10^6)和一个模式T(长度<=10^5),要求在主串S中找出与模式T相匹配的子串,返回相匹配的子串中的第一个字符在主串 ...
随机推荐
- gRPC学习之四:实战四类服务方法
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- Matlab 使用filter求解系统响应
MATLAB 提供了函数filter,可以实现差分方程的递规求解. 设差分方程的形式为\(a_0y(n) + a_1y(n-1) + \cdots + a_my(n-m)=b_0x(n)+b_1x(n ...
- 跟我一起写 Makefile(十三)
五.定义模式规则 你可以使用模式规则来定义一个隐含规则.一个模式规则就好像一个一般的规则,只是在规则中,目标的定义需要有"%"字符."%"的意思是表示一个或多个 ...
- TortoiseSVN日志字体和字号调整
TortoiseSVN提供的"show log"功能很有用,但默认的显示文件log历史的字体太小看不清,这个字体的设置在[TortoiseSVN ->Settings-> ...
- Linux 学习Shell一部分指令
接下来就是shell命令的一些演示了 set (超级多的变量和系统默认值) echo $?查看上一条指令是否执行成功 返回0意味着成功,返回1意味着失败 echo 是个 env 设置变量 解释一下上面 ...
- IDEA spring boot项目插件打包方式jar
一.打包 1.pom.xml中添加插件依赖 <build> <plugins> <plugin> <!--打包成可执行jar--> <groupI ...
- one_gadget的一些姿势
概要 one_gadget是libc中存在的一些执行execve("/bin/sh", NULL, NULL)的片段,当可以泄露libc地址,并且可以知道libc版本的时候,可以使 ...
- Mac 证书错误
在 Mac 操作系统安装 Python 3.6 或以上版本时,可能会遇到证书错误:Error: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify ...
- C# wpf中DataGrid 支持汇总行
最近有一个需求,需要汇总金额,份额等字段.我们使用的是原生的WPF控件,自己开发了一套Template.而没有使用比较成熟的第三方控件.所以这个功能得自己开发.并且要做成控件层次的功能. 当然也可以这 ...
- Mysql 中隐式转换
案例一:条件字段函数操作 假设你现在维护了一个交易系统,其中交易记录表 tradelog 包含交易流水号(tradeid).交易员 id(operator).交易时间(t_modified)等字段.为 ...