判断两个字符串s1和s2是否互为旋转词,互为旋转字符串?
提示:字符串匹配算法KMP算法,速度极快,其应用题完全适合在面试的时候用来优化!
之前咱们学过KMP算法:o(n)速度匹配两个字符串
还有KMP算法必须要预备的预设数组信息:next数组
(1)KMP算法预备知识:字符串match的每一个位置i之前的字符串,前缀与后缀匹配的最大长度是多少?
(2)KMP算法:在字符串s中搜索匹配查找match字符串,如果能找到返回首个匹配位置i,否则返回-1
这几个文章你必须学会了,学透了
才能搞懂本题怎么优化!
题目
判断两个字符串s1和s2是否互为旋转词,互为旋转字符串/
一、审题
示例:
s1=123456
s2=
234561
345612
456123
561234
612345
s1经过循环平移得到的任意一个字符串s2都是s1的旋转词,旋转字符串。
暴力解
自然就是按照定义来
从s1的i=0–N-1位置,每一个位置i,都跟s2从头到尾来对比一遍
如果s1长度N,s2长度M=N,因为旋转词起码长度相同的
那这算法复杂度就是o(N^2)
挺复杂度的,肯定不行
代码自然也不难:
//暴力解判别俩字符串是否互为旋转词,返回Boolean
public static boolean turnWord1(String str1, String str2){
if (str1.length() != str2.length()) return false;
char[] s1 = str1.toCharArray();
char[] s2 = str2.toCharArray();
boolean isTrun = false;
for (int i = 0; i < str1.length(); i++) {
int k = i;//从0位置开始
for (int j = 0; j < str2.length(); j++) {
if (j == 0) isTrun = s1[k] == s2[j];//第一次先给它true,因为我们需要连续多与,不能此次都有false
else isTrun &= s1[k] == s2[j];//持续与,最后看看都是true那就好
k = nextIndex(k, s1.length);
if (k == i) break;//若果回到了当初这个i位置,则退出本次字符串的对比,i到下一个位置
}
if (isTrun) break;//如果找到一个旋转词,退出就行
}
return isTrun;
}
//循环坐标;
public static int nextIndex(int i, int max){
return i + 1 == max ? 0 : i + 1;//上限时就返回0,否则i+1
}
public static void test(){
String str1 = "12345";
String str2 = "45123";
System.out.println(turnWord1(str1, str2));
}
true
优化解:KMP算法加速到o(n)
咱 这么处理,让s1尾部再拼一个s1
然后用KMP算法判断s2是否为s1的子串
这样,通过额外空间复杂度的消耗,用KMP加速就搞定了。
因为旋转词是平移的结果,自然,s1+s1里面就包含了s1的所有旋转词。【这是知识点,见过就学会它】
所以,这个题优化的关键在哪?KMP算法
而KMP算法的关键又在对s2建立next信息数组
(1)KMP算法预备知识:字符串match的每一个位置i之前的字符串,前缀与后缀匹配的最大长度是多少?
(2)KMP算法:在字符串s中搜索匹配查找match字符串,如果能找到返回首个匹配位置i,否则返回-1
这俩非常重要的基础知识点:一定给学透了,本题就迎刃而解!
KMP预设数组信息next(对s2建立的)【手撕代码】
//复习,构建s2的next数组,咱们要反复给它手撕,彻底搞会了
//next怎么推导?自己画个图,有i-1的next信息,求i好说,对比i-1位置和y=next[y]的字符
public static int[] writeNextInfo(char[] str){
int N = str.length;
int[] next = new int[N];
next[0] = -1;//next[1]=0默认了
int i = 2;//从2开始
int y = next[i - 1];//对比之前那个y2
while (i < N){
//对比i-1位置和y=next[y]的字符,相等就是y+1
if (str[i - 1] == str[y]){
next[i++] = y + 1;
y++;//y要自动往后挪动,继续比下一个位置,因为i和y2已经对比上了。
}else if (next[y] > 0) y = next[y];//有可跳转的地方才去
else next[i++] = 0;//没有位置可以跳转了,自然就是0
}
return next;
}
KMP算法,手撕代码
//熟练手撕KMP算法,思想很重要,代码真的超级简单的
public static int kmpMatchStr12(String s, String match){
if (s.equals("") || s.length() == 0 || s.length() < match.length()) return -1;
char[] str = s.toCharArray();
char[] m = match.toCharArray();
int N = str.length;
int M = m.length;
//对m构建next
int[] next = writeNextInfo(m);
//从xy=0开始对比,相等就往后挪,否则看y=next[y]【当然保证y!=-1才能,否则就只能让x++】
int x = 0;
int y = 0;
while (x < N && y < M){
if (str[x] == m[y]){
x++;
y++;
}else if (next[y] != -1) y = next[y];//舍弃0--j-1那段重复对比
else x++;//y都回到原点了,对不上就只能让x++
}
//xy谁越界都行,保证y=M才能匹配上,长度就是y,x-y就是起始位置
return y == M ? x - y : -1;//否则没有就是-1
}
破解本题,手撕代码
代码流程:
(1)咱 这么处理,让s1尾部再拼一个s1
(2)然后用KMP算法判断s2是否为s1的子串
非常简单,问题不大:
//判断s1和s2是否互为旋转词
public static boolean bothTurnWord(String s1, String s2){
if ((s1.equals("") && !s2.equals("")) ||
(!s1.equals("") && s2.equals("")) ||
s1.length() != s2.length()) return false;//长度都不一样
//(1)咱 这么处理,让s1尾部再拼一个s1
s1 += s1;
//(2)然后用KMP算法判断s2是否为s1的子串
return kmpMatchStr12(s1, s2) != -1;//不是-1才行哦
}
public static void test2(){
String s1 = "12345";
String s2 = "45123";
System.out.println(turnWord2(s1, s2));
System.out.println(bothTurnWord(s1, s2));
}
public static void main(String[] args) {
// test();
test2();
}
结果:
true
true
显然,一定要把KMP算法给熟悉透了,才能轻松搞定本题,本题也就是很简单的事情。
总结
提示:重要经验:
1)KMP算法的预设数组信息next,代表i之前前缀和后缀字符串的匹配长度,也是0–i-1上第一个与i字符不配的位置
2)MMP算法的思想非常非常简单,舍弃思想,那段已经匹配过的前缀串的重复比对,加速到o(n)
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。