【C/C++】796. 旋转字符串

353 阅读1分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情


题目链接:796. 旋转字符串

题目描述

给定两个字符串, s 和 goal。如果在若干次旋转操作之后,s 能变成 goal ,那么返回 true 

s 的 旋转操作 就是将 s 最左边的字符移动到最右边。 

  • 例如, 若 s = 'abcde',在旋转一次之后结果就是'bcdea' 。

提示:

  • 1 <= s.length, goal.length <= 100
  • s 和 goal 由小写英文字母组成

示例 1:

输入: s = "abcde", goal = "cdeab"
输出: true

示例 2:

输入: s = "abcde", goal = "abced"
输出: false

整理题意: 题目重点是需要理解对字符串的旋转操作,我们可以理解为将字符串首位连接起来形成一个环,然后在这个环内任意选取一个字母作为起始位置,然后绕环一圈得到的字符串,问对字符串s这样操作能否得到字符串goal

解题思路分析

  • 方法一:模拟 首先,如果 ssgoal\textit{goal} 的长度不一样,那么无论怎么旋转,ss 都不能得到 goal\textit{goal},返回 false\text{false}。在长度一样(都为 nn)的前提下,假设 ss 旋转 ii 位,则与 goal\textit{goal} 中的某一位字符 goal[j]\textit{goal}[j] 对应的原 ss 中的字符应该为 s[(i+j)modn]s[(i+j) \bmod n]。在固定 ii 的情况下,遍历所有 jj,若对应字符都相同,则返回 true\text{true}。否则,继续遍历其他候选的 ii。若所有的 ii 都不能使 ss 变成 goal\textit{goal},则返回 false\text{false}

复杂度分析
时间复杂度:O(n2)O(n^2),其中 nn 是字符串 ss 的长度。我们需要双重循环来判断。
空间复杂度:O(1)O(1)。仅使用常数空间。

  • 方法二:字符串搜索匹配(KMP算法) 首先,如果 ssgoal\textit{goal} 的长度不一样,那么无论怎么旋转,ss 都不能得到 goal\textit{goal},返回 false\text{false}。我们可以这么思考,ss 字符串最多旋转多少次,也就是我们可以选取多少个字符作为起始位置,显然当我们对字符串进行旋转操作 s.length()s.length() 次后将回到字符串本身,那我们可以将给定的字符串进行复制操作,将复制后的字符串添加到原字符串后面,也就是 s+ss + s,得到的新字符串包含了所有 ss 可以通过旋转操作得到的字符串,所以我们只需要对 goal\textit{goal} 字符串在 s + s\textit{s + s} 字符串中进行字符串匹配,检查goal\textit{goal} 字符串是否为s + s\textit{s + s} 字符串的子字符串即可。

复杂度分析
时间复杂度:O(n+m)O(n + m),其中 nn 是字符串 ss 的长度,mm 是字符串 goalgoal 的长度。
空间复杂度:O(n)O(n),其中 nn 是字符串 ss 的长度。

这里使用到了KMP算法,对KMP算法不太理解的可以看看这里:【C/C++】KMP算法
学习完KMP算法后拿这道题进行练手,强化和巩固对KMP算法的理解。

代码实现

  • 方法一:模拟
class Solution {
public:
    bool rotateString(string s, string goal) {
        //m为字符串s的长度,n为字符串goal的长度
        int m = s.size(), n = goal.size();
        //首先如果s和goal的长度不一样,那么无论怎么旋转,s都不能得到goal,返回false
        if (m != n) return false;
        //暴力匹配
        for (int i = 0; i < n; i++) {
            bool flag = true;
            for (int j = 0; j < n; j++) {
                if (s[(i + j) % n] != goal[j]) {
                    flag = false;
                    break;
                }
            }
            if(flag) {
                return true;
            }
        }
        return false;
    }
};
  • 方法二:字符串搜索匹配(KMP算法)
class Solution {
public:
    bool rotateString(string s, string goal) {
        //注意特殊情况,当s = "aa" goal = "a"时应该返回false
        //所以根据题目要求s和goal字符串长度应该相等才能匹配,否则直接返回false
        if(s.length() != goal.length()) return false;
        s = s + s;
        int n = goal.length();
        int nxt[n + 1];
        //获取模式串goal的getNext数组
        //初始化第0位的值,我们将其设成了-1,这只是为了编程的方便,并没有其他的意义。相当于把nxt整体右移一位
        nxt[0] = -1;
        //i表示当前模式串需要匹配的位置,j表示需要递归回溯(跳转)的位置
        int i = 0, j = -1;
        while(i < n){
            //匹配相等就继续
            if(j == -1 || goal[i] == goal[j]) nxt[++i] = ++j;
            //不匹配就回溯跳转,所以j的值永远不会超过i
            else j = nxt[j];
        }
        //字符串匹配,在新的字符串s(s + s)中匹配goal字符串
        int len = s.length();
        i = 0, j = 0;
        while(i < len && j < n){
            //如果j=-1表示goal字符串的首字母与当前s串的i不匹配
            if(j == -1 || s[i] == goal[j]) i++, j++;
            else j = nxt[j];
        }
        //如果goal字符串全部匹配成功,j会指向字符串末尾
        if(j == n) return true;
        else return false;
    }
};

总结

虽然题目很简单,很多方法都能解决,但是正是需要这样的简单题,让我们学会一题多解的思维,达到扩展思维的目的。虽然暴力模拟的做法可以提交通过,但是在做题的意义上只是得到了代码能力上的提升,如果能够通过这题扩展了自己思维和加强巩固已经学习过的算法(温故而知新,检验算法的学习和理解),那么这题的价值就有所不同了。


结束语

总有人因为害怕失败就拒绝尝试,却忘了最大的遗憾是未曾开始。生活中遇到的很多事,常常你弱它就强。面对无法绕过的人生难题,如果你总是选择逃避,就会一直是输家,唯有勇敢面对,才有可能会赢。