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相匹配的子串,返回相匹配的子串中的第一个字符在主串 ...
随机推荐
- Python语言系列-10-数据库
MySQL 基础环境准备 readme.txt 作者:Alnk(李成果) 版本:v1.0 安装mysql数据库 略 创建student库 # mysql> create database stu ...
- @ConfigurationProperties实现自定义配置绑定
@ConfigurationProperties使用 创建一个类,类名上方注解,配置prefix属性,如下代码: @ConfigurationProperties( prefix = "he ...
- Fiddler抓包实用非常详细,学会不要去做坏事~
为什么要先学fiddler?学习接口测试必学http协议,如果直接先讲协议,我估计小伙伴们更懵,为了更好的理解协议,先从抓包开始.结合抓包工具讲http协议更容易学一些. 抓firefox上https ...
- 阿里云rds分区
SELECT PARTITION_NAME,TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'xw_user_appl ...
- 事务种类jdbc,Hibernate,JTA事务
JDBC事务 String URL="jdbc:sqlserver://localhost:1433;databaseName=test2"; String USER=" ...
- Executor执行器
Executors: CachedThreadPool 将为每个任务创建一个线程. public class CachedThreadPool { public static void main(S ...
- Unable to instantiate org.apache.hadoop.hive.ql.metadata.SessionHiveMetaStoreClient
1.今天在进行hive测试的时候,发现hive一直进不去,并且报了这个错误. Unable to instantiate org.apache.hadoop.hive.ql.metadata.Sess ...
- 了解Flask
了解Flask 什么是Flask Flask 是一个微框架(Micro framework),所谓微框架,它就是很轻量级的,作者划分出了Flask应该负责什么(请求路由.处理请求.返回响应).不应该负 ...
- 如何从 vue-element-admin 迁移到 Fantastic-admin
// FIXME 链接更新 如果你还不知道 Fantastic-admin 是什么,那么我先用几张预览图给大家了解一番. 看来预览图,如果你感兴趣,可以点这里来详细了解并试用,这是一款完成度极高,开箱 ...
- opencv入门系列教学(六)图像上的算术运算(加法、融合、按位运算)
0.序言 这一篇博客我们将学习图像的几种算术运算,例如加法,减法,按位运算等. 1.图像加法 我们可以通过OpenCV函数 cv.add() 或仅通过numpy操作 res=img1+img2 res ...