题目

给定一个字符串\(S\),求\(M\)字符串是否是\(S\)字符串中的子串.如果是,返回\(M\)对应\(S\)的第一个下标,否则返回-1.

例如:S串为a b c d a b c d a b c d e

M串为a b c d e

结果:返回S串下标8.

个人想法

之前看过这种求子串的题,但是只在脑海中想象了一下,没有动手写出算法.

看到的时候心里就嘀咕肯定不能暴力求解,需要让子串\(M\)进行跳跃,但是没有具体地研究边界问题,也没有动手写一个字符串验证想法,导致想法很不靠谱.

之前的想法很粗糙:

挨个比较字符串,如果不相同,整体都跳跃,跳跃的方式是\(M\)串首位对其不匹配\(S\)串的下标位置.然后继续比较

大约就是这个样子,想到这就没继续往下想也没有去实现,这显然是错误的.跳跃是应该跳跃,但是不是那么粗糙的跳跃,假如字符串和子串是下面这种情况这种情况:

以我那种错误的想法是会产生bug的:

结果是\(S\)串不会包含\(M\)串,但事实上是包含的.(下文将目标串统一称作\(S\),子串统一称作\(M\))

那我们就应该考虑如何正确的跳跃.

KMP算法

知道了之前的想法是错误的,我们就应该找到符合各种边界的跳跃方式.只要确定了跳跃方式,KMP就很容易写出来了.KMP中提到了前缀表(或者最大相同前后缀)这些概念.前缀表暂时先不考虑,只谈谈如何进行跳跃才能满足边界.

继续回到之前的图,如果我们想要成功地找到下标,正确的跳跃方式应该是下图这样:

注意\(M\)串错误匹配项(\(M[5]\)元素d)之前的元素(蓝色填充的a,b).

好,为什么这么跳呢?

当确定\(S[5]\)和\(M[5]\)不匹配的时候,我们可以确定d之前的项都是匹配的.即\(S[0-4]\)和\(M[0-4]\)是匹配的.

​ \(M[0-4]\)

对于已经匹配的\(M[0-4]\),前缀几项与后缀几项相同(即\(M[0]\),\(M[1]\)和\(M[3]\),\(M[4]\)是相同的),那么把\(M\)平移使\(M[0]\)对齐\(M[3]\)之前所处的位置,这一定能确保\(M[0]\),\(M[1]\)和之前\(M[3]\),\(M[4]\)与\(S\)对应的\(S[3]\),\(S[4]\)是匹配的,即一定能确保\(M[0]\),\(M[1]\)和\(S[3]\),\(S[4]\)是匹配的.

平移

那我们如何利用这个特点跳跃呢,现在我们生成一个辅助的前后缀相同的数组.

a,b,c,a,b,d为例,

M产生的子串 相同前后缀个数 备注:就把相同前后缀数组称为\(D\)
a 0
a,b 0
a,b,c 0
a,b,c,a 1
a,b,c,a,b 2
a,b,c,a,b,d 0 最后一个匹配就可以返回了,所以用处不大

最后生成一个相同前后缀的数组可以与\(M\)对应起来

现在只需要解释一下如何使用相同前后缀数组进行跳跃,应该就能写出KMP的代码了.

当\(M[5]\)与\(S[5]\)不相同时,看一下前后缀数组\(D\)前一个位置的值,即\(D[4]\)的值为2,那么只需要将\(M[2]\)平移至\(M[5]\)的位置上就可以了.如果\(M[3]\)不匹配,后缀数组\(D\)的前一位\(D[2]\)是0,那么就把\(M[0]\)平移对其\(M[3]\)的位置就可以了.

假如\(M[0]\)不匹配的话,就直接往后跳一位,与\(S\)下一项比较.

KMP代码实现(JAVA)

整体思路就大致如此了,如果理解了应该可以按照思路写出代码了.

下面就贴一下代码实现,我只是简单地测试了一下,没有大量测试,但是整体思路应该大致如此,可能有些边界问题还没有考虑周全,欢迎指正.

	public static int getIndexOf(String s, String m) {
if (s == null || m == null || m.length() < 1 || s.length() < m.length()) return -1;
char[] ss = s.toCharArray(), ms = m.toCharArray();
int si = 0, mi = 0;
int[] next = getMatchArray(ms);
int q = next[mi];
while (si < ss.length && mi < ms.length) {
if (ss[si] == ms[mi]) {
si++;
mi++;
} else {
if (mi != 0) {
mi = next[mi - 1];
} else {
si++;
}
}
}
return mi == ms.length ? si - mi : -1;
} public static int[] getMatchArray(char[] ms) {
if (ms.length == 1) return new int[]{0};
int[] next = new int[ms.length];
next[0] = 0;
int j = 0, tail = 1;
while (tail < ms.length) {
j = next[tail] = ms[j] == ms[tail] ? (j + 1) : 0;
tail++;
}
return next;
} public static void main(String[] args) {
String str = "abcabcababaccc";
String match = "ababa";
System.out.println(getIndexOf(str,match));
}

KMP算法思路的更多相关文章

  1. KMP算法代码

    以下是本人根据上一篇博客随笔http://www.cnblogs.com/jiayouwyhit/p/3251832.html,所写的KMP算法代码(暂未优化),个人认为在基于上一篇博客的基础上,代码 ...

  2. Python 细聊从暴力(BF)字符串匹配算法到 KMP 算法之间的精妙变化

    1. 字符串匹配算法 所谓字符串匹配算法,简单地说就是在一个目标字符串中查找是否存在另一个模式字符串.如在字符串 "ABCDEFG" 中查找是否存在 "EF" ...

  3. KMP算法之next数组的求解思路

    2.next数组的求解思路 本部分内容转自:http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algo ...

  4. 简单有效的kmp算法

    以前看过kmp算法,当时接触后总感觉好深奥啊,抱着数据结构的数啃了一中午,最终才大致看懂,后来提起kmp也只剩下“奥,它是做模式匹配的”这点干货.最近有空,翻出来算法导论看看,原来就是这么简单(先不说 ...

  5. 萌新笔记——用KMP算法与Trie字典树实现屏蔽敏感词(UTF-8编码)

    前几天写好了字典,又刚好重温了KMP算法,恰逢遇到朋友吐槽最近被和谐的词越来越多了,于是突发奇想,想要自己实现一下敏感词屏蔽. 基本敏感词的屏蔽说起来很简单,只要把字符串中的敏感词替换成"* ...

  6. 数据结构与算法JavaScript (五) 串(经典KMP算法)

    KMP算法和BM算法 KMP是前缀匹配和BM后缀匹配的经典算法,看得出来前缀匹配和后缀匹配的区别就仅仅在于比较的顺序不同 前缀匹配是指:模式串和母串的比较从左到右,模式串的移动也是从 左到右 后缀匹配 ...

  7. 扩展KMP算法

    一 问题定义 给定母串S和子串T,定义n为母串S的长度,m为子串T的长度,suffix[i]为第i个字符开始的母串S的后缀子串,extend[i]为suffix[i]与字串T的最长公共前缀长度.求出所 ...

  8. 字符串模式匹配之KMP算法图解与 next 数组原理和实现方案

    之前说到,朴素的匹配,每趟比较,都要回溯主串的指针,费事.则 KMP 就是对朴素匹配的一种改进.正好复习一下. KMP 算法其改进思想在于: 每当一趟匹配过程中出现字符比较不相等时,不需要回溯主串的 ...

  9. 算法:KMP算法

    算法:KMP排序 算法分析 KMP算法是一种快速的模式匹配算法.KMP是三位大师:D.E.Knuth.J.H.Morris和V.R.Pratt同时发现的,所以取首字母组成KMP. 少部分图片来自孤~影 ...

随机推荐

  1. Python Numpy matplotlib Histograms 直方图

    import numpy as np import matplotlib.pyplot as plt mu,sigma = 2,0.5 v = np.random.normal(mu,sigma,10 ...

  2. 树行DP小结

    顾名思义:就是在树上做的DP,依据DFS的性质,在访问过儿子之后返回后将儿子的状态传递给父亲... 先看例题: 此题用贪心也能过,不过正解是DP. 对于树上的DP我们可以直接考虑最优解下各点的状态来方 ...

  3. 重学STM32---(十)之CAN通信(二)

    目录 前言 程序编写 主代码 测试 前言   CAN协议是非常难的,但是在stm32中却是简单的,只需要我们配置寄存器即可,,,即使这样,我在学习的时候也遇到了许多困难 程序编写 1.开时钟,不用说 ...

  4. hdu 3635 Dragon Balls(并查集)

    题意: N个城市,每个城市有一个龙珠. 两个操作: 1.T A B:A城市的所有龙珠转移到B城市. 2.Q A:输出第A颗龙珠所在的城市,这个城市里所有的龙珠个数,第A颗龙珠总共到目前为止被转移了多少 ...

  5. linux 文件描述符和inode 的理解和区别

    inode 或i节点是指对文件的索引.如一个系统,所有文件是放在磁盘或flash上,就要编个目录来说明每个文件在什么地方,有什么属性,及大小等.就像书本的目录一样,便于查找和管理.这目录是操作系统需要 ...

  6. clnt_create: RPC: Port mapper failure - Unable to receive: errno 113 (No route to host)

    修改文件 /etc/sysconfig/nfs将#MOUNTD_PORT=892开启防火墙端口:firewalld-cmd --add-port=892/tcp

  7. Vue&Element开发框架中增加工作流处理,工作流的各个管理页面的界面处理

    我在起前面的几篇随笔中,大概介绍了工作流的一些场景化处理,包括如何把具体业务表单组件化,并在查看和编辑界面中,动态加载组件内容,以及对于查看申请单的主页面,把审批.取消.发起会签.会签.批示分阅.阅办 ...

  8. vue脚手架项目如何在控制台打印组件实例

    需要在浏览器上安装拓展程序vue开发工具,安装好后在控制台上输入$vm即可打印vue组件实例对象. Vue2.3开发工具都有,可自行下载 百度网盘链接提取码:si5l

  9. spring定时任务ThreadPoolTaskScheduler使用注意事项之线程池大小

    背景 最近小伙伴解决了一个工单,描述为"手工推送案件无法推,提示token失效",当前工单状态为待关闭,解决方案为"东软接口不稳定造成的,东软的接口恢复正常后,问题解决& ...

  10. 注解@RunWith的作用

    @RunWith就是一个运行器 @RunWith(JUnit4.class)就是指用JUnit4来运行 @RunWith(SpringJUnit4ClassRunner.class),让测试运行于Sp ...