- 示例工程
- 教程
【免费下载链接】leetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
本篇技术指南以开源仓库 doocs/leetcode 中《程序员面试金典(第 6 版)》题解目录 lcci/01.09.String Rotation 的英文题解 README_EN.md 为骨架,系统讲解"字符串轮转(String Rotation)"这一经典字符串问题的判定思路、数学原理与多语言实现。读完本文,你将掌握"长度前置判定 + 双字符串拼接 + 一次子串检查"的 O(n) 解法,并能在 Python、Java、C++、Go、TypeScript、Rust、Swift 中直接写出可运行的答案。
一、题目概述:什么是字符串轮转
根据 README_EN.md 的题目描述,本问题要求给定两个字符串s1和s2,编写代码检查s2是否为s1旋转(rotation)后的结果。所谓"旋转",指将字符串的某个前缀整体移动到末尾后形成的新串。
示例 1(成立):
Input: s1 = "waterbottle", s2 = "erbottlewat" Output: True"waterbottle"从位置 6 处切割,把前缀"wat"挪到末尾即得到"erbottlewat"。
示例 2(不成立):
Input: s1 = "aa", s2 = "aba" Output: False"aba"不是"aa"的任何旋转结果,且两者长度不相等。
约束条件:
- 字符串长度范围:
0 <= s1.length, s2.length <= 100000 - 题目额外设问:能否只调用一次"检查子串"的方法就完成判定?这正是本节要解决的核心挑战。
该题对应《程序员面试金典(第 6 版)》第 1 章"数组与字符串"中的第 09 题,在仓库中位于 lcci/01.09.String Rotation/ 目录,官方难度标记为 Easy(简单)。
二、解法核心:长度判定 + 双字符串拼接
1. 第一重判定:长度不相等必非旋转
旋转只是把字符串内部元素重新排列位置,不会增加或减少任何字符。因此,若s1与s2长度不等,二者必然不是旋转关系,可以直接返回False。这一前置判定同时也是一个高效的剪枝:在字符串长度可高达 100000 的场景下,先比较长度能避免对大量不匹配输入执行昂贵的子串搜索。
2. 第二重判定:s1 + s1覆盖全部旋转情况
当两个字符串长度相等时,考察数学性质:将s1与自己拼接得到s1 + s1,该结果字符串必然包含s1的所有旋转形态。原因在于:s1的任意一次旋转,本质上是s1内部两个子串段 A、B 交换顺序(AB→BA),而BA恰好是AABB即(A)(B)(A)(B)中间部分B A的体现,因此总能在s1 + s1中连续找到。
于是原问题被归约为:判断s2是否为s1 + s1的子串,恰好只调用一次子串检查方法,完美满足题目设问。
原文以s1 = "aba"为例给出了直观演示:
# True s1 = "aba" s2 = "baa" s1 + s1 = "abaaba" ^^^ # False s1 = "aba" s2 = "bab" s1 + s1 = "abaaba""baa"是"abaaba"从下标 1 开始的连续子串(baa),判定成立;"bab"无法在"abaaba"中连续匹配到,判定不成立。
3. 复杂度分析
设n为字符串s1的长度:
- 时间复杂度:$O(n)$。长度比较为 $O(1)$,拼接
s1 + s1为 $O(n)$,子串匹配在主流语言的标准库实现(如 Python 的in、Java 的String.contains、C++ 的std::string::find)中平均为线性复杂度; - 空间复杂度:$O(n)$。主要开销来自拼接出的长度为 $2n$ 的临时字符串。
三、七种语言实现:仓库源码逐一解读
仓库在 lcci/01.09.String Rotation/ 目录下为每种语言提供了独立的 Solution 文件,与 README_EN.md 中的代码块一一对应,可直接复制提交。
Python3
Solution.py:
class Solution: def isFlipedString(self, s1: str, s2: str) -> bool: return len(s1) == len(s2) and s2 in s1 * 2Python 的in运算符对字符串执行子串包含检查;s1 * 2等价于s1 + s1。得益于and的短路求值,长度不等时不会执行拼接操作。
Java
Solution.java:
class Solution { public boolean isFlipedString(String s1, String s2) { return s1.length() == s2.length() && (s1 + s1).contains(s2); } }String.contains(CharSequence)内部基于indexOf实现子串查找。
C++
Solution.cpp:
class Solution { public: bool isFlipedString(string s1, string s2) { return s1.size() == s2.size() && (s1 + s1).find(s2) != string::npos; } };C++ 的std::string::find在未找到时返回string::npos,故以!= string::npos作为包含判定的条件。
Go
Solution.go:
func isFlipedString(s1 string, s2 string) bool { return len(s1) == len(s2) && strings.Contains(s1+s1, s2) }Go 需显式导入标准库strings包,strings.Contains返回布尔值。
TypeScript
Solution.ts:
function isFlipedString(s1: string, s2: string): boolean { return s1.length === s2.length && (s2 + s2).indexOf(s1) !== -1; }注意 TypeScript 版本的对称写法:拼接的是s2 + s2,检查s1是否为其子串。因为"旋转"是相互的——若s2是s1的旋转,则s1也必然是s2的旋转,所以(s2 + s2).indexOf(s1)与(s1 + s1).indexOf(s2)判定结果完全一致,这在实现层面进一步印证了拼接法的等价性。
Rust
Solution.rs:
impl Solution { pub fn is_fliped_string(s1: String, s2: String) -> bool { s1.len() == s2.len() && (s2.clone() + &s2).contains(&s1) } }Rust 的String::contains接受模式参数;由于拼接表达式需要移动/借用所有权,这里对s2进行了clone,同样采用了"以s2为拼接基准、检查s1"的对称写法。
Swift
Solution.swift:
class Solution { func isFlippedString(_ s1: String, _ s2: String) -> Bool { return (s1.isEmpty && s2.isEmpty) || (s1.count == s2.count && (s1 + s1).contains(s2)) } }Swift 版本额外处理了两个空字符串均为空的边界情形:s1 = ""、s2 = ""时,空串是空串的旋转,应返回True;若不处理,(s1 + s1).contains(s2)本身对两个空串也成立,但显式写出该分支让语义更清晰,避免count比较或空串拼接带来的边界歧义。
四、边界条件与易错点分析
结合题目约束(长度可达 100000)和 Swift 版本的特判,实战中需重点关注以下场景:
- 长度不等:如
s1 = "aa"、s2 = "aba",直接返回False——这是最容易被忽视也最重要的剪枝; - 两个空字符串:
s1 = s2 = ""应视为旋转成立; - 单个字符:
s1 = "a"、s2 = "a",拼接后"aa"包含"a",成立;s1 = "a"、s2 = "b",长度相等但拼接不包含,不成立; - 相同字符串:
s2 == s1时(旋转 0 次),s1 + s1必包含s1,成立; - 性能考量:长度上限为 100000,拼接产生的临时字符串长度为 200000,空间开销 O(n) 在题目约束下完全可接受;
and/&&短路求值确保长度不等时不会执行昂贵的拼接与搜索。
五、小结
面试题 01.09 字符串轮转的核心结论可浓缩为三步:
- 比较长度:不等则直接返回
False; - 拼接字符串:构造
s1 + s1(或对称地构造s2 + s2); - 一次子串检查:判断
s2是否包含于s1 + s1中,恰好只调用一次子串匹配方法,命中题目设问。
该解法时间复杂度 $O(n)$、空间复杂度 $O(n)$,是此类"旋转/循环位移"问题的通用范式——凡是"判断 B 是否由 A 旋转得到"的问题,都可以通过"拼接自身 + 子串包含"来归约。完整题解与多语言实现请查阅仓库 lcci/01.09.String Rotation/README_EN.md 及同目录下的各 Solution 文件,并可在 lcci/README_EN.md 中找到《程序员面试金典(第 6 版)》的全部题解索引。
- 示例工程
- 教程
【免费下载链接】leetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
相关推荐
conda 插件开发指南:用 PrefixData loaders 插件把非 conda 包接入 conda 生态
conda 插件开发指南:用 PrefixData loaders 插件把非 conda 包接入 conda 生态 PrefixData 是 conda 内部用
示例工程教程LeetCode-Book 题解精讲:LeetCode 796 旋转字符串(Rotate String)的拼接包含判定法
LeetCode Book 题解精讲:LeetCode 796 旋转字符串(Rotate String)的拼接包含判定法 导读 LeetCode 796「旋转字
示例工程doocs/leetcode 题解精讲:面试题 01.06 字符串压缩(双指针 Run-Length 编码)
doocs/leetcode 题解精讲:面试题 01.06 字符串压缩(双指针 Run Length 编码) 导读 本文基于 doocs/leetcode 仓库
示例工程教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考