当遇到匹配字符串的问题(例如力扣第28题.实现strStr()),KMP算法提供了O(M+N)的时间复杂度解。
问题描述:
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。
示例:
//示例1
输入:haystack = "hello", needle = "ll"
输出:2
//示例2
输入:haystack = "aaaaa", needle = "bba"
输出:-1
//示例3
输入:haystack = "", needle = ""
输出:0
朴素解法(时间复杂度O((N-M)*M); 空间复杂度O(1)):
遍历haystack,对每个点再遍历needle,出现匹配就返回haystack的索引,否则返回-1。
C++代码如下:
class Solution {
public:
int strStr(String ss, String pp) {
int n = ss.size(), m = pp.size();
// 枚举原串的「发起点」
for (int i = 0; i <= n - m; i++) {
// 从原串的「发起点」和匹配串的「首位」开始,尝试匹配
int a = i, b = 0;
while (b < m && ss[a] == pp[b]) {
a++;
b++;
}
// 如果能够完全匹配,返回原串的「发起点」下标
if (b == m) return i;
}
return -1;
}
}
KMP解法(时间复杂度O(N+M); 空间复杂度O(M)):
基本思想:
1.如果needle匹配失败,不必回退至一开始,只需回退至前缀跟失败点之前相同的点就行了,举个例子:needle:a b d a b c 。如果在c位置匹配失败了(失败点用?表示),那么说明haystack串在失败点?之前跟needle串c点之前都是匹配的,说明haystack的失败点前面是:***abdab?对比needle:abdabc,发现只要把needle回退到abd的状态就能继续比较了,如果?是d的话就可以继续走下去。。
2.从上面的分析可以知道匹配失败点回退的情况只与needle有关,因此新建数组next来储存needle串每个元素匹配失败后要跳转的点(要跳转到的点是needle前缀跟已扫描地haystack前缀相同的位置)。
思路图解来自宫水三叶:一文详解KMP。
C++代码如下:
class Solution {
// KMP 算法
// ss: 原串(string) pp: 匹配串(pattern)
public:
int strStr(String ss, String pp) {
if (pp.isEmpty()) return 0;
// 分别读取原串和匹配串的长度
int n = ss.size(), m = pp.size();
// 原串和匹配串前面都加空格,使其下标从 1 开始
ss = " " + ss;
pp = " " + pp;
// 构建 next 数组,数组长度为匹配串的长度(next 数组是和匹配串相关的)
array<int,pp.size()+1> next;
// 构造过程 i = 2,j = 0 开始,i 小于等于匹配串长度 【构造 i 从 2 开始】
for (int i = 2, j = 0; i <= m; i++) {
// 匹配不成功的话,j = next(j)
while (j > 0 && pp[i] != pp[j + 1]) j = next[j];//不相同就跳到上一个匹配点
// 匹配成功的话,先让 j++
if (pp[i] == pp[j + 1]) j++;
// 更新 next[i],结束本次循环,i++
next[i] = j;
}
// 匹配过程,i = 1,j = 0 开始,i 小于等于原串长度 【匹配 i 从 1 开始】
for (int i = 1, j = 0; i <= n; i++) {
// 匹配不成功 j = next(j)
while (j > 0 && ss[i] != pp[j + 1]) j = next[j];//匹配失败就跳到上一个匹配成功的点
// 匹配成功的话,先让 j++,结束本次循环后 i++
if (ss[i] == pp[j + 1]) j++;
// 整一段匹配成功,直接返回下标
if (j == m) return i - m;
}
return -1;
}
}
进阶题目:686. 重复叠加字符串匹配