前沿:撰写博客的目的是为了再刷时回顾和进一步完善,其次才是以教为学,所以如果有些博客写的较简陋,是为了保持进度不得已而为之,还请大家多多见谅。
预:看到题目后的思路和实现的代码。
见:参考答案展示。
感思:对比答案后的思考,与之前做过的题目是否有关联。
行:
(1)对于没做出来的题目,阅读答案后重新做一遍;
(2)下次做题可以尝试改善的方向;
(3)有助于理解的相关的题目
优先级:做题进度>学习&总结>默写回顾>做题数量
相关章节
方法论:
KMP三个大佬发明的,所以叫KMP方法
1.找出字符串中第一个匹配项的下标
题目链接:实现 strStr()
思考
为什么要使用KMP解决?
1.看到字符串匹配问题,使用KMP效率高,其能够在匹配错误后找到先前已经匹配子串中是否有合适的匹配位置,这样能够避免因匹配错误需要重头再开始匹配的重复性操作。
KMP如何实现避免重头匹配的?
KMP通过next数组实现,该数组将需要匹配的子串遍历一遍寻找每个下标的最长相等前后缀,即以遍历到的字符为结尾但不包含第一个字符的子串和以第一个字符为开头但不包括该遍历到字符为结尾的子串,寻找这两个子串能够保持相同的最长长度。
寻找到每个下标的最长相等前后缀,当其后面下标不匹配时,则能够直接跳到上个下标(后缀)的前缀对应下标继续判断是否匹配,因为最长相等前后缀的含义就是两段完全相等的子串,上个下标为后缀,而以第一个字符为开头的则为前缀,不匹配时则能够跳到前缀结尾的下标后的字符看是否要判断当前下标字符相同,若再不同则继续匹配。
如何实现next数组?
可以看到上面多次提到上个下标的前缀,因为当不匹配时,都是要跳到上个下标的前缀对应的下标位置,并且在next数组寻求匹配时,也是为了寻找前后缀最长相等,所以若以上个下标为遍历点,则能够减少赋值运算,本文章采用统一减一的思路实现next数组。
next数组的匹配是寻找子串每个下标的最长相等前后缀,所以需要遍历。
会出现两种情况:当前字符匹配和不匹配的情况
- 当匹配时,则前缀相应的下标j要加一(前缀当前字符被匹配,则匹配前缀下一个字符)
- 不匹配时
- 将下标j对应的next数组下标值赋予j,则能够实现跳转到相同的字符匹配位置再匹配j+1下标字符是否与当前i字符相同。
- 若不等则继续跳进行下次匹配,但至多跳到j = -1,此时已经匹配到第一个字符即跳到最前的最长前后缀相等匹配位置了。
- 当匹配结束后,当前j位置即为next[i]的最长相等前后缀下标位置;
int[] next = new int[len];
int j = -1;
next[0] = -1;
for(int i = 1;i < len;i++){
while(j >= 0 && needle.charAt(i) != needle.charAt(j+1)){
j = next[j];
}
if(needle.charAt(i) == needle.charAt(j+1)){
j++;
}
next[i] == j;
}
如何实现已匹配子串与字符串匹配?
将上面通过位置i获得的子串变成字符串相应信息即可
j = -1;
for(int i = 0;i < len;i++){
while(j >= 0 && needle.charAt(j+1) != haystack.charAt(i)){
j = next[j];
}
if(needle.charAt(j+1) == haystack.charAt(i)){
j++;
}
if(j == slen-1){
return i-slen+1;
}
}
- 注意一定要将j的值重新赋值为-1,代表着从头开始匹配,否则将会受先前寻找next数组下标的代码影响。
2.重复的子字符串
题目链接:459. 重复的子字符串
思考
如何寻找重复的子字符串?
该题与上一题的区别则是要先思考清楚这个问题,判断是否有重复,则是要将该字符串s再加上一个相同字符串s,将这个2s字符串的头和尾的字符去掉,即去除直接相加产生的两个相同字符子串的情况,之后再判断新字符2s-2是否能够匹配字符串s即可,若包含则有重复,不包含则不重复。
为什么这样就能判断是否有重复子字符串?
毛毛鱼大佬:证明还是好证的。
如果 s 可以由其子串 x 重复构成,重复的次数设为 n,则 s = nx,s + s = 2nx 移除开头和结尾的字符后破坏了两个子串 x,则在移除头尾后还剩 2n-2 个子串 如果遍历移除后的字符串发现 s 在其中,则有子串 x 的数量 2n - 2 >= n 则 n >= 2 所以 s 至少是 x 重复2次构成
实现代码
class Solution {
public boolean repeatedSubstringPattern(String s) {
//15min
int len = s.length();
if(len == 1) return false;
StringBuilder sb = new StringBuilder(s+s);
int[] next = new int[len];
int j = -1;
next[0] = j;
for(int i = 1;i < len;i++){
while(j >= 0 && s.charAt(i) != s.charAt(j+1)){
j = next[j];
}
if(s.charAt(i) == s.charAt(j+1)){
j++;
}
next[i] = j;
}
j = -1;
for(int i = 1;i < len*2-1;i++){
while(j >= 0 && sb.charAt(i) != s.charAt(j+1)){
j = next[j];
}
if(sb.charAt(i) == s.charAt(j+1)){
j++;
}
if(j == len-1){
return true;
}
}
return false;
}
}