在问 · 算法题解

796. 旋转字符串

简单字符串拼接 s + s 后查找子串力扣原题 ↗

题目描述

输入两个字符串 s 和 goal。

一次旋转是指把 s 的第一个字符挪到末尾。例如 "abcde" 旋转一次得到 "bcdea"。

如果对 s 旋转若干次(可以是 0 次)后能得到 goal,返回 true,否则返回 false。

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

解释:"abcde" 旋转一次得到 "bcdea",再旋转一次得到 "cdeab",正好等于 goal。

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

解释:"abcde" 的 5 种旋转结果是 "abcde"、"bcdea"、"cdeab"、"deabc"、"eabcd",其中没有 "abced"。

数据范围

  • 1 <= s.length, goal.length <= 100
  • s 和 goal 只含小写英文字母。

解题思路:拼接 s + s 后查找子串

把 s 旋转 k 次得到 s[k:] + s[:k]。把 s 自己拼接一次得到 s + s,它从下标 k 开始的长度为 n 的片段恰好就是 s[k:] + s[:k]。所以 s 的所有旋转都是 s + s 的子串。先确认两串长度相同,再判断 goal 是否在 s + s 中出现。

为什么正确

长度为 n 的串 goal 是 s 的某个旋转,当且仅当它是 s + s 的一个子串。

复杂度分析

时间 O(n²)(朴素子串查找,用 KMP 可降到 O(n)),额外空间 O(n)。

易错点

忘记比较长度会出错:s = "aa",goal = "a" 时 goal 是 s + s 的子串,但不是 s 的旋转。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def rotateString(self, s: str, goal: str) -> bool:
        if len(s) != len(goal):
            return False
        doubled = s + s
        return goal in doubled

载入编辑器,自己跑一遍 →

其他解法

内置查找写法最短,最坏 O(n²);KMP 把匹配降到 O(n),适合长字符串或不能用库函数的场合。

解法 2:s + s 上的 KMP 匹配 时间 O(n),空间 O(n)

同样利用“goal 是 s 的旋转 ⇔ 长度相等且 goal 是 s + s 的子串”。先求 goal 的前缀函数 fail[i](goal[0..i] 最长的相等真前缀和真后缀长度),再在 s + s 上扫描:字符相等就前进,不等就把已匹配长度回退到 fail[j - 1],文本指针从不后退。匹配长度达到 n 即为旋转。

最坏情况也是线性时间,不依赖语言内置查找的实现。本题 n ≤ 100,内置查找已经足够,KMP 代码更长,价值在于练习字符串匹配的通用技巧。

class Solution:
    def rotateString(self, s: str, goal: str) -> bool:
        n = len(s)
        if n != len(goal):
            return False
        fail = [0] * n
        j = 0
        for i in range(1, n):
            while j > 0 and goal[i] != goal[j]:
                j = fail[j - 1]
            if goal[i] == goal[j]:
                j += 1
            fail[i] = j
        text = s + s
        j = 0
        for c in text:
            while j > 0 and c != goal[j]:
                j = fail[j - 1]
            if c == goal[j]:
                j += 1
            if j == n:
                return True
        return False

字符串 专题的其他题目

查看 字符串 全部题目 →