leetcode_day9(补)

本文最后更新于 2024年8月18日 上午

找出字符串中第一个匹配项的下标

文章视频讲解:代码随想录 | 实现strStr()

在母串中寻找子串,最优的算法就是KMP算法,十分经典,也很复杂,笔者从大一程序设计课上初接触到KMP到今天,已经写过4次KMP了,而这一次尝试纯手撕,仍然花了两个小时,最后还是倒在next数组求法上。功不唐捐,希望下一次再见KMP,能直接手撕出来。 以下是个人偏感性理解,仅作参考

KMP更快的原因

暴力匹配就是把子串的头从0号一个一个往前加到m - n号,不匹配就加一。

而KMP不是无脑加一,会像动态规划那样记录,从而实现最优的最远的后移,最大程度减少匹配次数。

KMP像会记住之前已经匹配到了什么作为基业,在遇到挫折之后就不必从头开始,而是吃老本从基业再开始,直到把基业亏光再从头开始或者成功匹配。 ## KMP怎样记住哪些已经匹配过呢 KMP用到了最长公共前后缀来记录哪些已经匹配过了。代码随想录里的说法是前缀表next[] ### 什么是最长公共前后缀 一个string的前缀就是除开最后一个元素的所有子串

后缀就是除开第一个元素的所有子串

比如对于aacaab来说:

前缀从长到短有:aacaa,aaca,aac,aa,a

后缀从长到短有:acaab,caab,aab,ab,b

最长公共前后缀就是前后缀里面相同的里面最长那一个

什么是前缀表

个人理解:

对于string来说,它的前缀表肯定和它的长度相同,前缀表的每一个值代表以当前位置结束的子串的最长公共前后缀长度

比如对于串aabaac来说,其前缀表第一位就代表子串a的最长公共前后缀长度,即为0,因为只有一个,去掉第一个或者最后一个之后就没了;

其前缀表第二位就是1,代表子串aa的最长公共前后缀长度,因为去掉头尾之后都是a,而长度为1

所以aabaac的前缀表就为0 1 0 1 2 0

一个特点

如果子串的头尾都不相同,那么其最长公共前后缀长度肯定是0,因为其所有的前缀都有"头",所有的后缀都有"尾",肯定不会相同。

前缀表怎么用呢

之前说到KMP记住之前匹配过哪些,失败之后不用从头开始,而是从"基业"开始,这里的"基业"就是公共前后缀。

如果匹配失败,失败这一位之前都是成功的,将视野缩小到已经匹配上的子串上面,这个子串的后缀是可以匹配上的,那么如果存在相同的前缀,则可以把相同的前缀往前移到后缀的位置,必定仍然匹配,这样就一下子提前了很多位,实现了"保底"

以上是抽象感性说法,下面上严谨代码:

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {
public:
int strStr(string haystack, string needle) {
if(haystack.size() < needle.size()) return -1;
vector<int> prefix = next(needle);
int m = haystack.size(), n = needle.size();
int ans = -1, j = 0;
for(int i = 0;i <= m;) {
if(j == n) {
ans = i - n;
break;
}
else if(haystack[i] == needle[j]) {
j++;
i++;
continue;
}
else if(j > 0){
j = prefix[j - 1];//回退吃老本
}
else {
i++;
j = 0;
}
}
return ans;
}
vector<int> next(string s) {
vector<int> next(s.size(), 0);
int j = 0;
for(int i = 1;i < s.size();i++) {
/*这一行可太妙了*/while(j > 0 && s[i] != s[j]) j = next[j - 1];
if(s[i] == s[j]) j++;
next[i] = j;
}
return next;
}
};

重复的子字符串

文章视频讲解:代码随想录 | 重复的子字符串

对于前缀表的深入理解和应用,建议打印出测试用例的前缀表,一看便知:重复子串第一次出现地方全是0,后面单调递增1、2、3、4……

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
bool repeatedSubstringPattern(string s) {
vector<int> pre = next(s);
int len = s.size();
if(pre[len - 1] != 0 && len % (len - pre[len - 1]) == 0) return true;
return false;
}
vector<int> next(string s) {
vector<int> ans(s.size(), 0);
int j = 0;
for(int i = 1;i < s.size();i++ ){
while(j > 0 && s[i] != s[j]) j = ans[j - 1];
if(s[i] == s[j]) j++;
ans[i] = j;
}
return ans;
}
};

leetcode_day9(补)
https://novelyear.github.io/2024/08/18/leetcode-day9/
作者
Leoo Yann
更新于
2024年8月18日
许可协议