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. 从子串的可能长度为1开始,一直计算到长度的一半,也就是最大的可能子串

  2. 用if(length%i==0)初筛,如果字符串的长度不是该可能子串长度的整数倍,那么它必然不是可能子串

  3. 注意我们只计算粒度最小的子串,因为粒度更大的子串一定是由粒度最小的子串拼接而成的。所以,它的位置转换和小粒度的子串的位置转换是重复的

  4. 如果我们找到了一个最小粒度的子串,原字符串是由n个这样的子串拼接而成的,那么最终的输出就是n:包括n-1个前方向后方的移动和1个移动0次的结果

    例如:字符串为aaaa,则最小粒度的子串是a,那么就有移动前1个、前2个、前3个和移动0个这四种情况,所以输出是4.因为移动两个的情况就是子串aa的移动情况,所以我们不需要计算子串aa的情况

posted @ 2020-04-06 11:13  别再闹了  阅读(185)  评论(0)    收藏  举报