文章:代码随想录
状态:看完视频才能实现
leecode 28 实现str()
思路:
这道题就是实现字符串匹配算法。KMP的经典例题。我觉得KMP的终点就是理解next数组的形成,也是一个递推,kmp字符匹配的过程。
//next数组中找最大相同前后缀,其实也可以理解为Kmp的过程。从j位置起始遍历的看作字串,i位置开始的看作主串。 // 当i,j位置的字符相同时,他们同时往后遍历,每相同一个,他们的相同前后缀长度也会加一。 //但要知道相同前后缀长度的意义,可以理解为从前面拿几个数放到后面他们时相同的。 //所以当i,j位置的值不相同怎么办?我们要尝试看有没有长度更小的相同前后缀吧。所以我们看j-1位置的值,看不匹配位置之前我们所找到的最大前后缀的长度 //所以这意味着我们把字符串的前几个字符移到尾部都是相同的,那么在这个基础上再看下一个匹不匹配,如果匹配就直接j+1,不匹配还需要再往j-1的位置递推。 //不匹配的情况,可以直接让j指针指向j-1位置上的值。比如j-1的位置为1,那么意味着可以跳过一位字符来比较,所以当j指向下标1,那么其实就是需要被比较的字符的位置,0被跳过了。
其实next数组实现之后,kmp的流程实现起来也很类似,所以要理解这个过程。
代码:
//那么匹配子串的流程就是同时声明两个指针,一个指向主串,一个指向子串。核心就是让主指针不回退,子指针回退。
// 匹配则同时遍历,不匹配则找到next数组中,不匹配位置的上一个位置的值,让子指针回退到这个值的位置再重新开始遍历比较.
//这也是个递推的过程,从头部与尾部相同的最长前缀位置再往后遍历,不匹配则继续往前递推。这样就可以利用之前已经获取到的信息来遍历。
public static int strStr(String haystack, String needle) {
int res=-1;
int[] next=getNext(needle);
int j=0;
for (int i = 0; i < haystack.length(); i++) {
while(j>0 && haystack.charAt(i)!=needle.charAt(j)){j=next[--j];}
if(haystack.charAt(i)==needle.charAt(j)){
j++;
}
if(j==needle.length()){
res=i-j+1;
break;
}
}
return res;
}
public static int[] getNext(String needle) {
int[] next = new int[needle.length()];
int j = 0;
next[0] = 0;
for (int i = 1; i < needle.length(); i++) {
//next数组中找最大相同前后缀,其实也可以理解为Kmp的过程。从j位置起始遍历的看作字串,i位置开始的看作主串。
// 当i,j位置的字符相同时,他们同时往后遍历,每相同一个,他们的相同前后缀长度也会加一。
//但要知道相同前后缀长度的意义,可以理解为从前面拿几个数放到后面他们时相同的。
//所以当i,j位置的值不相同怎么办?我们要尝试看有没有长度更小的相同前后缀吧。所以我们看j-1位置的值,看不匹配位置之前我们所找到的最大前后缀的长度
//所以这意味着我们把字符串的前几个字符移到尾部都是相同的,那么在这个基础上再看下一个匹不匹配,如果匹配就直接j+1,不匹配还需要再往j-1的位置递推。
//不匹配的情况,可以直接让j指针指向j-1位置上的值。比如j-1的位置为1,那么意味着可以跳过一位字符来比较,所以当j指向下标1,那么其实就是需要被比较的字符的位置,0被跳过了。
while(j>0 && needle.charAt(j)!=needle.charAt(i) ){
j=next[--j];
}
if(needle.charAt(j)==needle.charAt(i)){ j++;}
next[i]=j;
}
return next;
leecode 48 重复的子字符串
思路:
有两种实现方法 移动匹配和KMP,kmp实现也有两种方法。
先说移动匹配:
//移动匹配 因为如果一个字符串s,是由一个子串s‘重复组成的话,那么这个主串的长度一定是s’的整数倍。 // 所以如果我们将主串拼接,并从1的索引位置开始寻找,如果最后最先找到的主串不是拼接进来的主串,则证明能被重复子串形成
代码:
public boolean repeatedSubstringPattern(String s) {
return (s + s).indexOf(s, 1) != s.length();
}
KMP:
有两种实现方法: //第一种是从s+s中对s进行模式串匹配,找到s第一次出现的位置,逻辑和上面一样,就是自己实现indexOf算法。 //第二种就是找到字符尾部时最长相同前后缀不包含的部分,如果len能整除那部分,那么那就是最小的重复子串。随想录上有证明。 //结论为当一个字符串由重复子串组成的,最长相等前后缀不包含的子串就是最小重复子串 即len-(next[len]);而如果是由重复子串组成的主串,最后位置的最长前后缀也是字符的最长相等前后缀。
代码:
public boolean repeatedSubstringPatternKMP(String s) {
int j=0;
int len=s.length();
int [] next=new int[len];
next[0]=0;
for (int i = 1; i <len ; i++) {
while(j>0 &&s.charAt(j)!=s.charAt(i)){
j=next[--j];
}
if (s.charAt(j)==s.charAt(i)){j++;}
next[i]=j;
}
//结尾处没有相同前后缀时一定不是相同子串重复组成, 需要对等于0时做特殊处理
if(next[len-1]>0 && len%(len- next[len-1])==0){
return true;
}
return false;
}