题目链接:28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)
初见思路:
就是简单的模拟吧,分别判断haystack和needle的长度,分情况进行讨论,定义boolean变量以进行第一个下标的保存,但只通过了73个用例,不知道这是出于什么原因
也没有使用KMP算法,就是非常暴力的逐个求解,不推荐使用
class Solution {
public int strStr(String haystack, String needle) {
if(haystack.length() == 0 ){
return -1;
}
if(
if(needle.length() > 1){
int res=0;
int judge = 0;
boolean first = false;
for(int i = 0; i < haystack.length();i++){
if((haystack.charAt(i) == needle.charAt(judge)) && (judge+1 < needle.length()) && (i+1 < haystack.length()) && (haystack.charAt(i+1) == needle.charAt(judge+1))){
if(!first){
res = i;
first = true;
}
judge++;
}
else{
if(judge == needle.length() - 1){
return res;
}
else{
first = false;
judge = 0;
}
}
}
return -1;
}
else if(needle.length() == 1){
for(int i = 0; i < haystack.length();i++){
if(haystack.charAt(i) == needle.charAt(0)) return i;
}
return -1;
}
else if(needle.length() > haystack.length() ){
return -1;
}
return -1;
}
}
KMP算法:
本质思想:当出现字符串不匹配时,记录之前的一部分已经匹配的文本内容,利用这些信息避免重头再来
正确的做法应当是使用KMP算法,这是由于KMP算法中存在一个next数组,即前缀表,他起到了一个回退的作用,这样做不至于当出现不同时重头再来,而是从一个更加优秀的位置进行重新匹配
前缀表:记录下表i之前(包括i)的字符串中,有多大长度的相同前缀后缀(其实就是为了更快的进行匹配拉)
前缀:不包含最后一个字符的所有以第一个字符开头的连续子串
后缀:不包含第一个字符的所有以最后一个字符结尾的连续子串
next数组的构造:
定义一个函数getNext来构造next数组,函数参数为一个指向next数组的指针和字符串构成
next数组的构造其实就只有三步:
-
初始化
定义两个指针 i 和 j,j指向前缀末尾位置,i指向后缀末尾位置,同时需要对next数组进行初始化赋值,如下int j = -1; next[0] = j;
-
处理前后缀不同的情况
i从1开始进行,否则无意义,进行s[i]与s[j+1]进行比较,若不相同,即此刻为前后缀不相同的情况,此刻需要进行回退,判断 j ≥ 0 ,令 j = next[j]即可
while(j >= 0 && s[i] != s[j+1]){
j = next[j];
}
- 处理前后缀相同的情况
若s[i] == s[j+1] 则同时向后移动i和j,并修改j未+1时前缀表next[]中的j+1的值
if(s[i] == s[j+1]{
j++;
}
next[i] = j;
综上所述
整个getNext函数如下:
public void getNext(int[] next, String s){
//初始化
int j = -1;
next[0] = j;
for(int i = 1;i < s.length();i++){
//处理前后缀不相同的情况
while(j >= 0 && s[j+1] != s[i]){
j = next[j];
}
//处理前后缀相同的情况
if(s[j+1] == s[i]){
j++;
}
next[i] = j;
}
}
使用next数组进行匹配
定义两个下表 i指向文本串起始位置,j指向模式串起始位置
j初始值仍然为-1,i从0开始遍历文本串,逐个进行比较,若s[i] ≠ t[j+1] 则 j = next[j]; 若s[i] == t[j+1] 则j++
当j == 文本串的长度时,则成功匹配
综上 匹配代码如下:
int j = -1;
for (int i = 0; i < haystack.length();i++){
if(j >= 0 && haystack.charAt(i) != needle.charAt(j+1){
j = next[j];
}
if( haystack.charAt(i) == needle.charAt(j+1)){
j++;
}
if( j == (t.length() -1)){
return (i - t.length() -1);
}
}
故本题ac代码如下:
class Solution {
public void getNext(int []next, String s){
int j = -1;
next[0] = j;
for(int i = 1;i < s.length();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;
}
}
public int strStr(String haystack, String needle) {
if(needle.length() == 0) return 0;
int []next = new int[needle.length()];
getNext(next,needle);
int j = -1;
for(int i = 0; i < haystack.length();i++){
whlie( (j >= 0) && (haystack.charAt(i) != needle.charAt(j+1))){
j = next[j];
}
if(haystack.charAt(i) == needle.charAt(j+1)){
j++;
}
if(j == needle.length()-1) return i - needle.length()+1;
}
return -1;
}
}
本题目相对较难,需要认真学习kmp算法后才能够体会其中内容
题目链接:459. 重复的子字符串 - 力扣(LeetCode)
初见思路:即求出一个相等前后缀,他们的长度相加是否等于该字符串的长度,若相等则该字符串能够由一个子串重复组成,否则不可以组成
本题中我想的是找到一个最长的相等前后缀,*2后判断长度是否等于原字符串长度,以此来判断是否能够重复组成无疑是无比错误的,如abababab这个字符串 最长相等前后缀为6,这样做忽略了重复的影响,不该这样考虑
正确的做法应当是求出最小的重复子串,即两个最长相等前后缀的不包含的子串,如下图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w1cjrOjc-1668494909097)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/2b7b78c4-83fe-4a79-97b9-9cd3ca2998b1/Untitled.png)]
这里我们设最小重复子串为x 则整个字符串 为 nx 设最长相等前后缀为mx
则必然有 nx - mx = x
所以如果有 n % (n-m) == 0 ,则有最小重复子串能够组成整个字符串
而本题中,若next[len-1]的值不为-1,则说明存在重复的前后缀
其次再判断 length % (length - (next[len-1] + 1)) 的值是否为0,若为0则说明存在最小重复子串能够组成整个字符串
ac代码如下:
class Solution {
public void getNext(int[] next,String s){
int j = -1;
next[0] = j;
for(int i = 1; i < s.length();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;
}
}
public boolean repeatedSubstringPattern(String s) {
if(s.length() == 0) return false;
int[] next = new int[s.length()];
getNext(next,s);
int len = s.length();
if(next[len-1] != -1 && len % (len - next[len-1]-1) == 0){
return true;
}
else return false;
}
}
字符串总结
Java中的String是一个引用类型变量,其值是不可变的,因此我们通常使用StringBuffer来对原有的String类进行操作,常用的获取下标函数为s.charAt(i) ,获取长度使用s.length()
库函数要不要使用?
在解题过程中,若这个函数只是起到很小的一部分作用,且读者本身理解库函数内部的实现原理,那么就可以放心大胆的使用,否则建议不要使用库函数,而是去亲手实现
双指针法:
双指针在数组、链表和字符串中均有广泛的使用,如数组填充等问题,都可以预先扩充数组,随后由后往前进行操作
反转:
- 当需要按照固定的规律进行处理字符串时,应当想着在for循环上做文章,以此完成固定规律的处理
- 同时可以通过 整体-局部反转的操作完成诸如左旋、 反转字符串中单词的效果
KMP:
kmp算法解决的核心问题就是子串的匹配问题 其使用的核心就在于next[]前缀表的创建 以及匹配,了解了这两点后并加以思考就能解决大多数的子串匹配问题了