LeetCode.796:力扣
这个题虽然被力扣规划为简单难度,是因为它下限低,可以依赖String类的stratWith()方法,很轻松解决,但是用stratWith()方法学不到什么东西,最好可以用这个题练一下自己对KMP算法的掌握程度。
思路:这个题意思就是经过若干次旋转得到goal目标字符串。首先如果两个字符串长度不相等(长度一大一小),再怎么旋转都得不到goal字符串。然后骚的点来了:将原来的s字符串变成两倍,若是在里面能够找到goal字符串,那么就可以通过旋转得到goal字符串。
分析:为什么?为什么将原来的s字符串扩大两倍(eg:“abc” ---> "abcabc"),这样在里面寻找目标字符串就行了。因为将原来的s字符串扩大两倍之后,不管旋转多少次的字符串,都是两倍字符串的字串。(eg:“abc” ---> "abcabc",goal:“cab”。"abcabc"由0位置开始找三个是“abc”、由1位置开始找三个是“bca”、由2位置找三个是“cab”、由3位置找三个是“abc”,出现循环了)。将原来的字符串扩大两倍的字符串包含目标字符串的所有旋转结果。
方法一:投机取巧利用startWhit()方法
遍历两倍的那个字符串,以每一个位置作为开头,判断是否有目标字符串。
public static boolean rotateString(String s, String goal) {
if (s.length() != goal.length()) {
return false;
}
String string = s + s;
for (int i = 0; i < string.length(); i++) {
if (string.startsWith(goal, i)) {
return true;
}
}
return false;
}
方法二:KMP算法(重点)
KMP算法的大思路也是遍历每一个位置,但是有一个小加速过程。
next数组:next数组和原string字符串等长,每一个位置的含义:当前位置字符之前,最大的前缀==最大的后缀,next[i]==这个最大的前(后)缀字符数,(eg:“ababc”这个字符串,对c位置来说,前缀和后缀有很多,前缀“a”、“ab”、“aba”;后缀:“b”、“ab”、“bab”,最大前缀“ab”,最大后缀“ab”,为什么是“ab”而不是“aba”或者“bab”?因为这两个前缀和后缀根本不相等,需要前缀==后缀,那么对于c位置(i==4位置)来说,next[i] ==2)。这个就是next数组的含义。
next数组的生成:
public static int[] getNextArr(char[] chars) {
if (chars.length == 1) {
return new int[]{-1};
}
int[] next = new int[chars.length];
next[0] = -1;//next数组前两个位置的值分别是-1,0,没有例外
int i = 2;//i的值表示该在哪个位置求next数组的值(遍历chars)
int cn = 0;//cn代表哪个位置该和i-1相比较
while (i < chars.length) {
if (chars[i - 1] == chars[cn]) {
next[i++] = ++cn;
} else if (cn > 0) {//cn还没有等于0,还能往左移,然后去匹配
cn = next[cn];
} else {//cn不能左移了,而且当前位置没有和之前的next[cn]匹配成功
next[i++] = 0;
}
}
return next;
}
有了next数组,接下来实现KMP算法:
假设string字符串到了i位置开始和目标字符串进行匹配,string从i位置开始、goal从0位置开始,直到遇到一个位置(此时string来到x位置、goal来到y位置),二者的字符不相等。因为有next数组,所以我们知道:next[y],即y位置之前的最大前缀和后缀(前缀==后缀),那么将goal向后推,让string的x位置和goal的next[y]位置进行比较,若相等,则两个位置都加一,继续匹配,不相等则x位置和goal的next[next[y]]位置进行比较,直到匹配成功可以继续匹配,或者next数组来到了0位置,此时说明当前string 的x位置不能匹配出一个完整的goal,则string去往下一个位置(即i+1)的位置,以此类推。
实现KMP算法的代码:
public static int KMP(String string, String goal) {
char[] str1 = string.toCharArray();
char[] str2 = goal.toCharArray();
int x = 0;//string字符串该哪个位置开始比对了
int y = 0;
int[] next = getNextArr(str2);
while (x < str1.length && y < str2.length) {
if (str1[x] == str2[y]) {
x++;
y++;
} else if (next[y] == -1) {
x++;//去string字符串中下一个该匹配的位置开始比对
} else {
y = next[y];//y缩小开始比对
}
}
//如果是因为str1越界退出的while循环,那么说明尝试了所有的str1位置都不能匹配出完整的str2
return y == str2.length ? x - y : -1;
}
力扣796题KMP算法提交结果: