String Shifting(今日头条2017秋招真题)
String Shifting(今日头条2017秋招真题)
题目描述
我们规定对一个字符串的shift操作如下:
shift(“ABCD”, 0) = “ABCD”
shift(“ABCD”, 1) = “BCDA”
shift(“ABCD”, 2) = “CDAB”
换言之, 我们把最左侧的N个字符剪切下来, 按序附加到了右侧。
给定一个长度为n的字符串,我们规定最多可以进行n次向左的循环shift操作。如果shift(string, x) = string (0<= x <n), 我们称其为一次匹配(match)。求在shift过程中出现匹配的次数。
输入
输入仅包括一个给定的字符串,只包含大小写字符。
样例输入
byebyebye
输出
输出仅包括一行,即所求的匹配次数。
样例输出
3
时间限制
C/C++语言:1000MS其它语言:3000MS
内存限制
C/C++语言:65536KB其它语言:589824KB
本题一开始的思路偏了,写出来是这样的:
package Test;
import java.util.Scanner;
public class Test6 {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
String string=sc.next();
int length=string.length();
if(length<2){
System.out.println(length);
return;
}else if(length==2){
if(string.charAt(0)==string.charAt(1)){
System.out.println(2);
return;
}else{
System.out.println(1);
return;
}
}
int mid=length/2;
int count=1;
//从i处开始剪
for(int i=1;i<length-1;i++){
if(i<mid){
String left=string.substring(0,i);
if(string.substring(i,2*i).equals(left)&&string.substring(length-i).equals(left)){
count++;
}
}else if(i>mid){
String right=string.substring(i);
if(string.substring(0,length-i).equals(right)&&string.substring(2*i-length,i).equals(right)){
count++;
}
}else{
if(string.substring(0,i).equals(string.substring(i))){
count++;
}
}
}
System.out.println(count);
}
}
说对吗,其实是对的。但是仔细想一想,如果不整这些复杂的,而是直接将前面的换到后面进行比较,是不是也是求两个subString一个equals,和上面的其实复杂度是一样的,这就是绕弯子了。
而答案其实就是在直接做的基础上多做了一步,因为直接做的话会超时,那么就做一个简单的判断,看旧的开头和新的开头字符是不是一样、旧的结尾字符和新的结尾字符是不是一样,只有一样才继续比较,这样就减少了不少情况,就是这么简单
package Test;
import java.util.Scanner;
public class Test61 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String s = scanner.next();
int num = 1;
char a = s.charAt(0);//头
char b = s.charAt(s.length() - 1);//末尾
if (s.length() < 700000)
for (int i = 1; i < s.length(); i++) {
/**
* s.charAt(i) == a表示新的开头和旧的开头要一样
* s.charAt(i - 1) == b表示新的结尾和旧的结尾一样
* 其实就是在直接做的基础上做了一些小小的判断,从而减少了情况,节约时间
*/
if (s.charAt(i) == a && s.charAt(i - 1) == b) {
String temp = s.substring(i) + s.substring(0, i);
if (temp.equals(s))
num++;
}
}
System.out.println(num);
scanner.close();
}
}
但是最后发现这是个投机取巧的办法,因为有一个最长的字符串长度是超过700000的,输出的答案正好是1,所以才不超时的,不靠谱
本题的完美做法如下:
分析思路和我一开始的做法其实是一脉相承的:

这样,我们就得到了一个非常非常重要的结论:如果一个字符串从某一处切开,将两部分交换后仍与原字符串相等,则其一定是一个基于某一子串的循环的字符串。这个结论足以将本题变为模板题,适用于所有的此类问题
import java.util.Scanner;
public class Main{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String string = sc.next();
int length = string.length();
if(length<2){
System.out.println(length);
return;
}else if(length==2){
if(string.charAt(0)==string.charAt(1)){
System.out.println(2);
return;
}else{
System.out.println(1);
return;
}
}
int ans= 1;
for (int i = 1; i <= length/2; i++) {
if(length%i==0){
int count=0;
int j=i;
while(j<length&&(string.substring(j,j+i).equals(string.substring(0,i)))){
count++;
j+=i;
}
if(j>=length){
ans+=count;
break;
}
}
}
System.out.println(ans);
}
}
上面的代码有几个要点:
-
从子串的可能长度为1开始,一直计算到长度的一半,也就是最大的可能子串
-
用if(length%i==0)初筛,如果字符串的长度不是该可能子串长度的整数倍,那么它必然不是可能子串
-
注意我们只计算粒度最小的子串,因为粒度更大的子串一定是由粒度最小的子串拼接而成的。所以,它的位置转换和小粒度的子串的位置转换是重复的
-
如果我们找到了一个最小粒度的子串,原字符串是由n个这样的子串拼接而成的,那么最终的输出就是n:包括n-1个前方向后方的移动和1个移动0次的结果
例如:字符串为aaaa,则最小粒度的子串是a,那么就有移动前1个、前2个、前3个和移动0个这四种情况,所以输出是4.因为移动两个的情况就是子串aa的移动情况,所以我们不需要计算子串aa的情况

浙公网安备 33010602011771号