深入理解KMP算法
创始人
2025-05-30 22:02:59
0

看这个算法之前,希望你做过:"leetcode-28-找出字符串中第一个匹配项的下标" 这一道题。

题目描述(直接上例子):
主串(T):leetcode
字串(P):leeto
在主串(T)中找出包含字串(P)的最小下标。

BF算法(暴力)

BF算法:下一个主串与子串不匹配时,满足以下条件👇
1. 主串回退到此次开始匹配位置+1处;
2. 子串回退到0位置。
  1. 第一次比较

主串在'c'处与子串在'o'处不匹配
1. 主串回退到此次开始匹配位置+1,即是下标为1的位置;
2. 子串回退到0位置。

  1. 第二次比较

主串在'e'处与子串在'l'处不匹配
1. 主串回退到此次开始匹配位置+1,即是下标为2的位置;
2. 子串回退到0位置。

  1. 第三次比较

主串在'e'处与子串在'l'处不匹配
1. 主串回退到此次开始匹配位置+1,即是下标为3的位置;
2. 子串回退到0位置。

  1. 第四次比较

主串在't'处与子串在'l'处不匹配
1. 主串回退到此次开始匹配位置+1,即是下标为4的位置;
2. 子串回退到0位置。

下一次比较子串长度已经大于主串长度,不可能包含字串了,所以结束比较。

从上面的算法可以得出时间复杂度为:O((n-m)*m),其中n为主串字符串长度,m为子串字符串长度。

class Solution {public int strStr(String haystack, String needle) {int n = haystack.length();      // 主串(T)长度int m = needle.length();        // 子串(P)长度// 两个特例if (n < m) return -1;           // 子串(P)大于主串(T)if (haystack.equals(needle)) {  // 子串(P)等于主串(T)return 0;}int end = n - m;                // 控制提前结束,就是剩下的主串长度小于子串长度,不用比了,肯定不相等for (int i = 0; i <= end; i++) {if(haystack.charAt(i) != needle.charAt(0)) {continue;               // 子串开头就不等于主串的开头,跳过比较}int k = 0;                  // 子串与主串相等的字符个数for (int j = i; j < i+m; j++, k++) {if(haystack.charAt(j) != needle.charAt(k)){break;}}if(k == m){                 // 一共有m个相等,即是找到答案啦,返回下标return i;}}return -1;}
}

KMP算法

KMP算法:下一个主串与子串不匹配时,满足以下两个条件👇
1. 主串不回退;
2. 子串回退到特定位置。

理解2(子串回退到特定位置)之前,首先要了解一下"最长公共前后缀",这里是子串回退到哪的重点。

最长公共前后缀

首先,我先通过一个例子解释一下"最长公共前后缀",因为这个词可能表达得不是很正确,但是我找不到比这个更合适得词来描述这种情况了。

例如,上图中的例子,其中字符串为 "leet"。

前缀:包含第一个单词,不包含后一个单词的连续子串,则为前缀。
lee:包含第一个单词"l",不包含最后一个单词"t"
le:包含第一个单词"l",不包含最后一个单词"t"
l:包含第一个单词"l",不包含最后一个单词"t"

后缀:包含最后一个单词,不包含第一个单词的连续子串,则为后缀。
eet:包含最后一个单词"t",不包含第一个单词"l"
et:包含最后一个单词"t",不包含第一个单词"l"
t:包含最后一个单词"t",不包含第一个单词"l"

有了字符串的"前后缀"概念之后,我们可以找出子串的最长公共前后缀,例如上述例子中,字符串的最长公共前后缀为:0。因为没有一个前、后缀子串是相等的。

为了加深理解,再举一个例子:

图中例子,字符串为:"abab",则最长的公共前后缀为2,因为,前缀"ab"=后缀"ab"。

回退数组

理解了最长公共前后缀,其实子串与主串匹配失败,应该回退到哪个位置,已经出现答案了。
举个例子:
主串:abeabababeabf
子串:abeabf

由图可得,主串在'a'处于子串在'f'处不相等,按照BF算法的回退过程,应该如下图所示:

按KMP算法,回退如下图所示:

其实,这里就运用到"最长公共前后缀"的知识进行回退的。

主串在'a'处于子串在'f'处不相等,那么,前面字符串"abeab"其实已经之前比较过了(是相等的),那我们只需要找"abeab"的最长公共前后缀位置的下一个下标位置即可(这一步很关键,我尝试过多次入门KMP,但是都被这一步拒之门外,虽然这样做没错,但是为什么可以这么做??这其实也是我之前一直想知道为什么的一步,下面做一个解释)。

解释以下为什么只要找到最长公共前后缀位置的下标就是回退的位置!

匹配错误的字符串"abeaba",是在最后一个位置"a"处匹配错误的,是不是可以理解成有一对组合它以"a"为结尾与子串匹配不上。那我是不是可以找另一对组合,不是以"a"结尾的,但是前面是相同的。那我看一下前面有没有(看的这一步就是最长公共前后缀)。

由图可得,找到了一对"ab",匹配错误的子串是"aba",但是这一对是"abe"。那我们就回退到最长公共前后缀位置的下一个下标位置即可。

class Solution {public int strStr(String haystack, String needle) {//int n = haystack.length();      // 主串(T)长度//int m = needle.length();        // 子串(P)长度//两个特例//if (n < m) return -1;           // 子串(P)大于主串(T)//if (haystack.equals(needle)) {  // 子串(P)等于主串(T)//    return 0;//}//////int end = n - m;                // 控制提前结束,就是剩下的主串长度小于子串长度,不用比了,肯定不相等//for (int i = 0; i <= end; i++) {//    if(haystack.charAt(i) != needle.charAt(0)) {//        continue;               // 子串开头就不等于主串的开头,跳过比较//    }//    int k = 0;                  // 子串与主串相等的字符个数//    for (int j = i; j < i+m; j++, k++) {//        if(haystack.charAt(j) != needle.charAt(k)){//            break;//        }//    }//    if(k == m){                 // 一共有m个相等,即是找到答案啦,返回下标//        return i;//    }//}////return -1;int n = haystack.length();      // 主串(T)长度int m = needle.length();        // 子串(P)长度// 两个特例if (n < m) return -1;           // 子串(P)大于主串(T)if (haystack.equals(needle)) {  // 子串(P)等于主串(T)return 0;}int[] next = getNext(needle);   // 获取回退数组for (int i = 0; i < m; i++) {   // 打印回退数组System.out.println(next[i]);}int i = 0;                      // 主串下标int j = 0;                      // 子串下标while (i < n){if(haystack.charAt(i) == needle.charAt(j)) {i++;j++;if(j == m) {return i-m;}} else {j = next[j];            // 不相等,则子串进行回退if(j == -1){            // 到了第一个子串位置j++;i++;}}}return -1;}public int[] getNext(String str){int n = str.length();int[] next = new int[n];next[0] = -1;for (int i = 2; i < n; i++) {String tempStr = str.substring(0, i);int right = i-1;int left = 1;int find = 0;while (left < i){String leftStr = tempStr.substring(0, right);String rightStr = tempStr.substring(left, i);if (leftStr.equals(rightStr)){find = leftStr.length();break;}left++;right--;}next[i] = find;}return next;}
}

相关内容

热门资讯

50岁生日祝福语 五十岁生日... 50岁生日祝福语 五十岁生日贺词人生感叹,10岁时,无忧无虑,天真无邪,20岁时,忙碌奔波,辛苦工作...
<Linux开发> linux... <Linux开发> linux开发工具-之-CMake简单例程[再见] Cmake相关文章如下: 1...
国庆节简单祝福语 2022年国庆节简单祝福语(精选155句)  在现实生活或工作学习中,大家都不可避免地会接触到祝福语...
母亲节丈母娘祝福语 母亲节丈母娘祝福语(精选175句)  在学习、工作或生活中,许多人都有过写祝福语的经历,对祝福语都不...
同事离职祝福语 同事离职祝福语15篇  在平平淡淡的学习、工作、生活中,大家都用到过祝福语吧,祝福语是指对人们的美好...
JAVASE(3.18) 目录 ​编辑 1.抽象类和抽象方法 2.接口 3.比较自定义类型 学习不要眼高手低,...
教师节优美祝福语短信 教师节优美祝福语短信55条  因为有了您,世界才会如此美丽,因为有了您,我的生命才会如此多彩!医生治...
去除Spire.Doc导出字样... //去除Spire.Doc导出字样信息try (FileInputStream in = n...
给老师的春节贺卡祝福语 给老师的春节贺卡祝福语170句  在我们平凡的日常里,要用到祝福语的情况还是蛮多的,祝福语可以起到增...
父亲节暖心祝福语 父亲节暖心祝福语  在日复一日的学习、工作或生活中,大家都用到过祝福语吧,祝福语有助于促进交流,拉近...
温馨教师节祝福语 2020年温馨教师节祝福语集锦45条  您辛劳了,教师节到了,您也该歇一歇了,坐着接接电话看看短信吧...
《RabbitMQ高阶知识》—... 《RabbitMQ高阶知识》— 消息可靠性 文章目录《RabbitMQ高阶知识》— 消息可靠性&#x...
Kubernetes(5):P... 我们一般将pod对象从创建至终的这段时间范围称为pod的生命周期,它主要包含下面的过程: pod创建...
学校领导新年元旦祝福语 学校领导新年元旦祝福语校师生员工们:  新年的钟声Ji荡着神州大地,岁月的航船开启着新的征程,我们即...
hugginface相关数据集... swaption2009/20k-en-zh-translation-pinyin-hsk 翻译 S...
孙子满月酒贺词 孙子满月酒贺词  宝宝降生,我前来贺喜,愿新生的小宝贝给你们带来数不尽的`快乐,祝小宝贝身体健康,茁...
温馨端午节祝福语句 常用温馨端午节祝福语句70句  端午快乐,幸福甜蜜!下文是小编特意为各位读者准备的温馨端午节祝福语句...
SQL常用语法语句 文章目录1. 什么是sql语句?1.1 DDL(数据定义语言)用法2. 什么是数据库对...
暖心重阳节祝福语短信 2020年暖心重阳节祝福语短信大汇总77句  九九重阳节日到,我的短信首先到,登高望远先敬老,赏菊插...
经典七夕祝福语 经典七夕祝福语99句  枯草连天太苍茫,小路白杨一行行。牵手偶因手太凉,曾经一起看夕阳。夕阳挂在柳梢...