要解决的问题

假设字符串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数组信息,假设ynext数组信息是2,如下图

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

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

如果x位置的值依旧不等于0位置的值,则宣告本次匹配失败,str串来到x下一个位置,match串从0位置开始继续匹配。

next数组求解

next数组的求解是KMP算法中最关键的一步,要快速求解next数组,需要做到当我们求i位置的next信息时,能通过i-1next数组信息加速求得,如下图

当我们求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++;
}
}

我们考虑这三个分支对于yy - x变化范围的影响

分支 y y - x
x++; y++ 推高 不变
x = next[x] 不变 推高
y++ 推高 推高

如上分析,yy-x都不可能降低,且三个分支只能中一个,所以,而yy-x的最大值均为N,所有分支执行总推高的次数不可能超过2N。即得出主流程的复杂度O(N)

KMP算法应用

求一个字符串的旋转词(详见:LeetCode 796)

思路

将这个字符串拼接一下, 比如原始串为:123456,拼接成:123456123456

如果匹配的字符串是这个拼接的字符串的子串,则互为旋转词。

一棵二叉树是否为另外一棵二叉树的子树(详见:LeetCode 572)

思路

先将两棵树分别序列化为数组A和数组B,如果B是A的子串,那么A对应的二叉树中一定有某个子树的结构和B对应的二叉树完全一样。

更多

算法和数据结构笔记

参考资料

KMP算法解决字符串匹配问题的更多相关文章

  1. Sunday算法解决字符串匹配问题

    概述 提起字符串匹配可能更多人会想到KMP算法,该算法时间复杂度为O(m+n),而且也是我们在学习数据结构过程中最早接触到的比较好的算法.但KMP算法需要在模式字符串有关联的情况下,也即模式字符串前后 ...

  2. 【KMP算法】字符串匹配

    一.问题 给定两个字符串S(原串)和(模式串)T,找出T在S中出现的位置. 二.朴素算法 当S[i] != T[j]时,把T往后移一位,回溯S的位置并重新开始比较.    (1) 成功匹配的部分(AB ...

  3. 【算法】字符串匹配之Z算法

    求文本与单模式串匹配,通常会使用KMP算法.后来接触到了Z算法,感觉Z算法也相当精妙.在以前的博文中也有过用Z算法来解决字符串匹配的题目. 下面介绍一下Z算法. 先一句话讲清楚Z算法能求什么东西. 输 ...

  4. Java实现 蓝桥杯 算法提高 字符串匹配

    试题 算法提高 字符串匹配 问题描述 给出一个字符串和多行文字,在这些文字中找到字符串出现的那些行.你的程序还需支持大小写敏感选项:当选项打开时,表示同一个字母的大写和小写看作不同的字符:当选项关闭时 ...

  5. 【数据结构与算法】字符串匹配(Rabin-Karp 算法和KMP 算法)

    Rabin-Karp 算法 概念 用于在 一个字符串 中查找 另外一个字符串 出现的位置. 与暴力法不同,基本原理就是比较字符串的 哈希码 ( HashCode ) , 快速的确定子字符串是否等于被查 ...

  6. 运用kmp算法解决的一些问题的简单题解

    学习kmp算法我最后是看的数据结构书上的一本教材学会的..我认为kmp相对于普通的BF算法就是避免了非常多不必要的匹配.而kmp算法的精髓自然就在于next数组的运用...而next数组简而言之就是存 ...

  7. Boyer Moore算法(字符串匹配)

    上一篇文章,我介绍了KMP算法. 但是,它并不是效率最高的算法,实际采用并不多.各种文本编辑器的"查找"功能(Ctrl+F),大多采用Boyer-Moore算法. Boyer-Mo ...

  8. C++编程练习(7)----“KMP模式匹配算法“字符串匹配

    子串在主串中的定位操作通常称做串的模式匹配. KMP模式匹配算法实现: /* Index_KMP.h头文件 */ #include<string> #include<sstream& ...

  9. 利用KMP算法解决串的模式匹配问题(c++) -- 数据结构

    题目: 7-1 串的模式匹配 (30 分) 给定一个主串S(长度<=10^6)和一个模式T(长度<=10^5),要求在主串S中找出与模式T相匹配的子串,返回相匹配的子串中的第一个字符在主串 ...

随机推荐

  1. 用kivy学习制作简易调色画板app

    制作一款简易的调色画板,要用到的知识:页面布局.ToggleButton.ToggleButtonBehavior.get_color_from_hex(兼容十六进制编码颜色):功能上要可以选择颜色, ...

  2. PHPMailer 远程命令执行漏洞 Writeup

    漏洞概述 1.漏洞简介 PHPMailer 小于5.2.18的版本存在远程代码执行漏洞.成功利用该漏洞后,攻击者可以远程任意代码执行.许多知名的 CMS 例如 Wordpress 等都是使用这个组件来 ...

  3. ARM的九种寻址方式

    文章目录 1.立即数寻址 2.寄存器寻址 3.寄存器间接寻址 4.寄存器偏移寻址 5.寄存器基址变址寻址 6.批量寄存器寻址 7.相对寻址 8.堆栈寻址 9.块拷贝寻址 寻址方式就是CPU根据指令中的 ...

  4. SpringBoot快速入门(一)

    本文内容 SpringBoot概述 SpringBoot快速入门 SpringBoot起步依赖原理分析 SpringBoot配置 SpringBoot整合其他框架 1.SpringBoot概述 1.1 ...

  5. nacos config基本使用

    说明 Nacos is an easy-to-use dynamic service discovery, configuration and service management platform ...

  6. CSS3图片倒影技术

    http://bbs.itheima.com/thread-330315-1-1.html?wymlxt

  7. webGis概念

    参考:https://blog.csdn.net/qq_36375770/article/details/80077533 参考:https://blog.csdn.net/BuquTianya/ar ...

  8. c# 对 struct为什么不能继承类和结构的思考

    1.类.结构在使用的时候可以不调用构造函数,如果能够继承类,这种情况下不能够初始化基类,因为不执行构造函数 2.结构.所有的结构类型都默认是 sealed,通过 反汇编可以看到  ,这就阻止了结构被继 ...

  9. 13.SpringMVC之全局异常

    我们知道,系统中异常包括:编译时异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发.测试通过手段减少运行时异常的发生.在开发中,不管是dao层 ...

  10. MAC下Jetbrains编译器无法打开问题解决

    这段时间不知道怎么回事,每次打开Rider必定闪退,毫无头绪,只好暂时放弃使用Rider,试用了一段时间Visual Studio. 可惜...虽然大学时候觉得VS天下第一,但是用惯了JB的编译器,再 ...