28. 实现 strStr() (本题一刷可以跳过)(基本了解KMP,需要二刷
刷题建议:因为KMP算法很难,第一遍了解大概思路,二刷的时候,再看KMP会 好懂很多。
题目链接:28. 实现 strStr()
文章讲解/视频讲解:28. 实现 strStr()
思路
用KMP算法,将暴力解法的时间复杂度O(m*n)降低为O(m+n)
KMP算法主要是解决字符串匹配问题的
本体主要分为2步:
- 构造Next[]数组也就是前缀表
- 初始化
- 处理前后缀不相同的情况
- 处理前后缀相同的情况
- 利用next数组来做匹配在文本串s里 找是否出现过模式串t。
- 初始化:定义两个下标j 指向模式串起始位置,i指向文本串起始位置
- 匹配过程
- 如果 s[i] 与 t[j + 1] 不相同,j就要从next数组里寻找下一个匹配的位置
- 如果 s[i] 与 t[j + 1] 相同,那么i 和 j 同时向后移动
如果j指向了模式串t的末尾,那么就说明模式串t完全匹配文本串s里的某个子串了。
class Solution {
public int strStr(String haystack, String needle) {
if(needle.length() == 0) return 0;
int[] next = new int[needle.length()];
getNext(next, needle);
// 下标j 指向模式串起始位置,i指向文本串起始位置
int j = 0;
for(int i = 0; i < haystack.length(); i++){
// s[i] 与 t[j + 1] 不相同,j就要从next数组里寻找下一个匹配的位置
while(j > 0 && needle.charAt(j) != haystack.charAt(i))
j = next[j - 1];
// 如果 s[i] 与 t[j + 1] 相同,那么i 和 j 同时向后移动
if(needle.charAt(j) == haystack.charAt(i))
j++;
// 如果j指向了模式串t的末尾,那么就说明模式串t完全匹配文本串s里的某个子串了
// 所以返回当前在文本串匹配模式串的位置i 减去 模式串的长度,就是文本串字符串中出现模式串的第一个位置。
if(j == needle.length())
return i - needle.length() + 1;
}
return -1;
}
private void getNext(int[] next, String s){
int j = 0;
next[0] = 0; // 初始化next[]数组
for(int i = 1; i < s.length(); i++){
while(j > 0 && s.charAt(j) != s.charAt(i)) // 前后缀不同
j = next[j - 1]; // 向前回退
if(s.charAt(j) == s.charAt(i)){ // 找到相同的前后缀
j++;
}
next[i] = j; // // 将j(前缀的长度)赋给next[i]
}
}
}
459.重复的子字符串 (本题一刷可以跳过)(需要二刷
本题算是KMP算法的一个应用,不过 对KMP了解不够熟练的话,理解本题就难很多。
我的建议是 KMP和本题,一刷的时候 ,可以适当放过,了解怎么回事就行,二刷的时候再来硬啃
题目链接:459.重复的子字符串
文章讲解/视频讲解:459.重复的子字符串
KMP解法
在由重复子串组成的字符串中,最长相等前后缀不包含的子串就是最小重复子串
next 数组记录的就是最长相同前后缀(这里的next数组是以统一不减一的方式计算的)
数组长度为:len。
如果数组长度len % (len - (next[len - 1] )) == 0 ,则说明数组的长度正好可以被 (数组长度-最长相等前后缀的长度) 整除 ,说明该字符串有重复的子字符串。(也就是说数组长度减去最长相同前后缀的长度相当于是第一个周期的长度,也就是一个周期的长度,如果这个周期可以被整除,就说明整个数组就是这个周期的循环。)
解题步骤
- 构造 next 数组
- 判断是否是重复的子字符串,即判断数组长度len % (len - (next[len - 1] )) == 0
// 代码随想录 KMP
class Solution {
public boolean repeatedSubstringPattern(String s) {
if (s.equals("")) return false;
int len = s.length();
// 原串加个空格(哨兵),使下标从1开始,这样j从0开始,也不用初始化了
s = " " + s;
char[] chars = s.toCharArray();
int[] next = new int[len + 1];
// 构造 next 数组过程,j从0开始(空格),i从2开始
for (int i = 2, j = 0; i <= len; i++) {
// 匹配不成功,j回到前一位置 next 数组所对应的值
while (j > 0 && chars[i] != chars[j + 1]) j = next[j];
// 匹配成功,j往后移
if (chars[i] == chars[j + 1]) j++;
// 更新 next 数组的值
next[i] = j;
}
// 最后判断是否是重复的子字符串,这里 next[len] 即代表next数组末尾的值
if (next[len] > 0 && len % (len - next[len]) == 0) {
return true;
}
return false;
}
}
// 代码随想录 移动匹配
public class Solution {
public boolean repeatedSubstringPattern(String s) {
String t = s + s;
t = t.substring(1, t.length() - 1); // 掐头去尾
return t.contains(s);
}
}
遗留问题
这道题视频中KMP的具体推导过程不太理解,需要二刷
字符串总结篇
文章讲解:[字符串总结]
(https://programmercarl.com/%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%80%BB%E7%BB%93.html )
双指针法是字符串处理的常客
KMP算法是字符串查找最重要的算法
双指针总结
文章讲解:双指针
文章中一共介绍了leetcode上九道使用双指针解决问题的经典题目,除了链表一些题目一定要使用双指针,其他题目都是使用双指针来提高效率,一般是将O(n^2)的时间复杂度,降为 O ( n ) O(n) O(n)。