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相匹配的子串,返回相匹配的子串中的第一个字符在主串 ...
随机推荐
- 浅谈C#取消令牌CancellationTokenSource
前言 相信大家在使用C#进行开发的时候,特别是使用异步的场景,多多少少会接触到CancellationTokenSource.看名字就知道它和取消异步任务相关的,而且一看便知大名鼎鼎的Cancella ...
- python中两种拷贝目录方法的比较
首先是用python自己的api: shutil.copytree('./build/tested/doc', './build/tested/build/doc') 优点是改变平台时不需要修改代码, ...
- Use Emacs as Personal Knowledge Base
http://stackoverflow.com/questions/2014636/how-to-maintain-an-emacs-based-knowledge-base
- SpringMVC学习07(Ajax)
7.Ajax研究 7.1 简介 AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML). AJAX 是一种在无需重新加载整个网页的情况 ...
- 【笔记】使用scikit-learn解决回归问题
使用sklearn解决回归问题 依然是加载数据 import numpy as np import matplotlib.pyplot as plt from sklearn import datas ...
- CAS 5.3使用MySQL数据库验证
一.本例环境说明 JDK 1.8 CAS 5.3 apache-maven-3.6.0 mysql-5.6.32 二.CAS 5.3基础环境搭建与验证 需要按照<CAS 5.3服务器搭建> ...
- CLR、CLI、CTS、CLS的关系
网站:https://blog.csdn.net/dodream/article/details/4719578 ·CLR(公共语言运行库)是一个CLI的实现,包含了.NET运行引擎和符合CLI的类库 ...
- PL/SQL 安装使用
PL/SQL 安装 前提:安装Oracle 使用 登录 oracle连接地址格式 ip:端口/ServerName,如192.168.136.130:1521/xe 新建菜单 Sql-Window 编 ...
- 从0开始搭建一个IoC容器(C#版)
网址:https://blog.csdn.net/wangyahua1234/article/details/100619695 目录 1. IoC简介 2. Tiny版IoC的功能 3. Tiny版 ...
- Ubuntu防火墙:ufw
原始linux的防火墙是iptables,以为过于繁琐,各个发行版几乎都有自己的方案; ubuntu下的防火墙是ufw[ubuntu fireward的缩写],centos的防火墙是fireward ...