今天一共学习字符串相关的两道题,个人认为需要学习的内容不少,难度也较大,分别是LeetCode.28和LeetCode.459。
LeetCode.28找出字符串中第一个匹配项的下标

本题和前面的题一样,都可以暴力破解。使用两层for循环将字符串中的每一个子串遍历出来,和待匹配项比较,返回第一次匹配成功的下标,如果遍历完字符串都没有匹配成功,则返回-1。
public static int strStr(String haystack,String needle){
for (int i = 0; i < haystack.length(); i++) {
if (haystack.charAt(i)==needle.charAt(0)) {
for (int j = i; j <haystack.length(); j++) {
if (haystack.substring(i,j+1).equals(needle)){
return i;
}
}
}
}
return -1;
}
注意,该算法的时间复杂度为O(N*M),N为字符串的长度,M为待匹配串的长度。而这种类型的题目,关于在一个字符串中寻找子串,有一个专门的算法,叫做KMP算法。这个算法在学数据结构的时候就学到过,不过只学了理论,实际的代码实现并没有思考过,今天重点内容就是这个KMP算法。
这个算法一共有两步,第一步是构建next数组,第二步是按照next数组寻找子串,KMP算法的时间复杂度为O(N+M)。
第一步,构建next数组。next数组有三种实现方式,第一种为原始实现,第二种将next[0]置成-1,并将后方原始实现的数组元素整体后移,第三种是将原始数组的next每一个元素-1。第二种元素在代码实现和理解上会有稍稍的改进,但也是需要了解最原始的实现方式,这里使用第一种也就是最原始的实现方式。这里将会直接说next的数组实现方式,具体原理可以参考印度老师的视频,我认为是讲的最清晰的,在b站上或者油管都可以搜到。首先定义一个长度和待匹配字符串长度相同的整型数组。将数组的第一个元素置为0,定义两个指针,j指向字符串首元素,i指向字符串第二个元素。如果i和j指向元素相等,那么next[i]的值为j+1,接着将j和i都向后移动一位。如果i和j指向元素不相等,并且j>0时,即在j不指向首元素且i,j指向元素不相等时,将j指向next数组种j前一位处,即j=next[j-1],如果还不相等,继续重复回溯,直到相等,同样这时next[i]=j+1。如果一直回溯到j=0,这时如果相等,那么同样next[i]=j+1,也就是这时next[i]的值为1。如果j=0所指元素仍不相等,则next[i]=0,将i后移一位,继续匹配。
public static int[] getNext(String s){
int[] next=new int[s.length()];
next[0]=0;
int j=0;
for (int i = 1; i <s.length() ; i++) {
while(j>0&&s.charAt(j)!=s.charAt(i)){
j=next[j-1];
}
if (s.charAt(i)==s.charAt(j)){
next[i]=j+1;
j++;
}else{
next[i]=0;
}
}
return next;
}
第二步是使用next数组进行匹配两个字符串,两个字符串分别是字符串haystack和待匹配子串needle。过程和第一步很像,首先使用两个指针,i指向haystack,j指向needle。这个过程中,haystack一直向前,needle如果碰到不相等的地方将会进行回溯,而回溯的地方不再是暴力破解时回溯到起始位置,而是根据next进行部分回溯或者全部回溯。如果i指向元素和j指向元素相等,两个指针同时向后移动,如果不相同,仍然看next数组中j前面一位的值,将j回溯到该处,继续判断是否和i指向元素相等,若是相等,则j和i继续向后移动匹配,如果还不相等,继续按next数组回溯。直到将needle字符串中的每一个元素都匹配完,则认为匹配成功,返回对应下标。如果将haystack遍历完成后仍未匹配到,则认为匹配不成功,根据题目要求,返回-1。
public static int strStrKMP(String haystack,String needle){
int[] next=getNext(needle);
int j=0;
int i=0;
for (; i < haystack.length(); i++) {
while(j>0&&haystack.charAt(i)!=needle.charAt(j)){
j=next[j-1];
}
if (haystack.charAt(i)==needle.charAt(j)){
j++;
}
if(j>=needle.length()){
return i-needle.length()+1;
}
}
return -1;
}
LeetCode.459重复的子字符串

这道题的思路是,将两个字符串s拼接成一个字符串ss,如s="abcdeabcde",那么ss="abcdeabcdeabcdeabcde"。接着将ss“掐头去尾”,将第一个元素和最后一个元素去掉,在ss中寻找是否有和s相等的子串,如果有,则认为s是由重复的子字符串构成,反之则不是。
为什么要“掐头去尾”,因为如果不掐头去尾,则以前组成ss的s也会被匹配到,寻找子串的操作可以借鉴上一题的KMP算法。
public static int[] getNext(String s){
int[] next=new int[s.length()];
next[0]=0;
for (int j=0,i=1;i<s.length();i++) {
while(j>0&&s.charAt(i)!=s.charAt(j)){
j=next[j-1];
}
if (s.charAt(i)==s.charAt(j)){
next[i]=j+1;
j++;
}else{
next[i]=0;
}
}
return next;
}
public static boolean contain(String haystack,String needle){
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-1];
}
if (haystack.charAt(i)==needle.charAt(j)){
j++;
}
if (j>=needle.length()){
return true;
}
}
return false;
}
public static boolean repeatedSubstringPattern(String s){
StringBuilder sb=new StringBuilder(s+s);
sb.delete(0,1);
sb.delete(sb.length()-1,sb.length());
return contain(sb.toString(),s);
}
1257

被折叠的 条评论
为什么被折叠?



