一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情。
题目链接:796. 旋转字符串
题目描述
给定两个字符串, s 和 goal。如果在若干次旋转操作之后,s 能变成 goal ,那么返回 true 。
s 的 旋转操作 就是将 s 最左边的字符移动到最右边。
- 例如, 若
s = 'abcde',在旋转一次之后结果就是'bcdea'。
提示:
1 <= s.length, goal.length <= 100s和goal由小写英文字母组成
示例 1:
输入: s = "abcde", goal = "cdeab"
输出: true
示例 2:
输入: s = "abcde", goal = "abced"
输出: false
整理题意: 题目重点是需要理解对字符串的旋转操作,我们可以理解为将字符串首位连接起来形成一个环,然后在这个环内任意选取一个字母作为起始位置,然后绕环一圈得到的字符串,问对字符串s这样操作能否得到字符串goal。
解题思路分析
- 方法一:模拟 首先,如果 和 的长度不一样,那么无论怎么旋转, 都不能得到 ,返回 。在长度一样(都为 )的前提下,假设 旋转 位,则与 中的某一位字符 对应的原 中的字符应该为 。在固定 的情况下,遍历所有 ,若对应字符都相同,则返回 。否则,继续遍历其他候选的 。若所有的 都不能使 变成 ,则返回 。
复杂度分析
时间复杂度:,其中 是字符串 的长度。我们需要双重循环来判断。
空间复杂度:。仅使用常数空间。
- 方法二:字符串搜索匹配(KMP算法) 首先,如果 和 的长度不一样,那么无论怎么旋转, 都不能得到 ,返回 。我们可以这么思考, 字符串最多旋转多少次,也就是我们可以选取多少个字符作为起始位置,显然当我们对字符串进行旋转操作 次后将回到字符串本身,那我们可以将给定的字符串进行复制操作,将复制后的字符串添加到原字符串后面,也就是 ,得到的新字符串包含了所有 可以通过旋转操作得到的字符串,所以我们只需要对 字符串在 字符串中进行字符串匹配,检查 字符串是否为 字符串的子字符串即可。
复杂度分析
时间复杂度:,其中 是字符串 的长度, 是字符串 的长度。
空间复杂度:,其中 是字符串 的长度。
这里使用到了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;
}
};
总结
虽然题目很简单,很多方法都能解决,但是正是需要这样的简单题,让我们学会一题多解的思维,达到扩展思维的目的。虽然暴力模拟的做法可以提交通过,但是在做题的意义上只是得到了代码能力上的提升,如果能够通过这题扩展了自己思维和加强巩固已经学习过的算法(温故而知新,检验算法的学习和理解),那么这题的价值就有所不同了。
结束语
总有人因为害怕失败就拒绝尝试,却忘了最大的遗憾是未曾开始。生活中遇到的很多事,常常你弱它就强。面对无法绕过的人生难题,如果你总是选择逃避,就会一直是输家,唯有勇敢面对,才有可能会赢。