LeetCode字符串轮转:从O(n^2)暴力到KMP O(n)优化的完整实战
本文详细解析LeetCode字符串轮转问题,对比暴力模拟与KMP优化的效率差异,并提供Java与C++的标准实现代码,重点阐述如何通过子串拼接与模式匹配将时间复杂度降至线性。
题目要求
思路一:双指针(模拟)
该方法的核心思想是模拟所有可能的旋转位置。对于长度为 n 的字符串,共有 n 种可能的轮转起始点。我们依次以每个索引为起点,遍历整个字符串并与目标串比对。
- 判断逻辑:外层循环确定旋转偏移量 i,内层循环逐个比对字符。利用取模运算
(i + j) % n处理越界回绕,若所有对应字符均相等则认为匹配成功。 - 复杂度分析:需要双重循环遍历,时间复杂度为 O(n^2),空间复杂度为 O(1)。
Java
class Solution {
public boolean isFlipedString(String s1, String s2) {
if (s1.length() != s2.length())
return false;
int n = s1.length();
if (n == 0)
return true;
for (int i = 0; i < n; i++) {
boolean res = true;
for (int j = 0; j < n; j++) {
if (s1.charAt((i + j) % n) != s2.charAt(j)) {
res = false;
break;
}
}
if (res)
return true;
}
return false;
}
}C++
class Solution {
public:
bool isFlipedString(string s1, string s2) {
if (s1.size() != s2.size())
return false;
int n = s1.size();
if (n == 0)
return true;
for (int i = 0; i < n; i++) {
bool res = true;
for (int j = 0; j < n; j++) {
if (s1[(i + j) % n] != s2[j]) {
res = false;
break;
}
}
if (res)
return true;
}
return false;
}
};思路二:子串匹配
字符串轮转的本质是一个子串匹配问题。如果字符串 s1 经过若干次轮转后等于 s2,那么 s2 必定是 s1 + s1 的子串。我们可以利用这一性质,采用更高效的匹配算法来替代暴力扫描。
手写 KMP 算法
KMP 算法通过在预处理阶段构建部分匹配表(next 数组),在匹配失败时利用已匹配的信息避免重复检查,从而将时间复杂度降为线性。以下分为 get_next 方法生成 next 数组和主匹配逻辑两部分实现。
Java - get_next
目的:构建 next 数组记录模式串的最长前后缀长度,并执行主匹配。
class Solution {
public boolean isFlipedString(String s1, String s2) {
if (s1.length() != s2.length())
return false;
int n = s1.length();
if (n == 0)
return true;
// 1. 构建 Next 数组
int[] nxt = new int[n];
nxt[0] = -1;
int j = 0; // pat指针
int k = -1; // 跳转位置
while (j < n - 1) {
if (k == -1 || s2.charAt(j) == s2.charAt(k)) {
if (s2.charAt(++j) == s2.charAt(++k))
nxt[j] = nxt[k]; // 连续相同,跳跃前缀的next
else
nxt[j] = k;
}
else
k = nxt[k];
}
// 2. 文本搜索 (txt = s1+s1)
String txt = s1 + s1;
j = 0;
for (int i = 0; i < 2 * n; i++) {
if (j < n && txt.charAt(i) == s2.charAt(j))
j++;
else {
j = nxt[j];
if (j == -1)
j++;
}
if (j == n)
return true;
}
return false;
}
}C++ - get_next
class Solution {
public:
bool isFlipedString(string s1, string s2) {
if (s1.size() != s2.size())
return false;
int n = s1.size();
if (n == 0)
return true;
int nxt[n];
nxt[0] = -1;
int j = 0; // pat指针
int k = -1; // 跳转位置
while (j < n - 1) {
if (k == -1 || s2[j] == s2[k]) {
if (s2[++j] == s2[++k])
nxt[j] = nxt[k];
else
nxt[j] = k;
}
else
k = nxt[k];
}
string txt = s1 + s1;
j = 0;
for (int i = 0; i < 2 * n; i++) {
if (j < n && txt[i] == s2[j])
j++;
else {
j = nxt[j];
if (j == -1)
j++;
}
if (j == n)
return true;
}
return false;
}
};基于 DP 的自动机法
这是一种更为通用的子串匹配构建方式。它使用一个二维数组 dp[state][char],表示在匹配了 state 个字符后,遇到新字符 char 时应跳转到的下一个状态。这种方法本质上构建了确定性有限状态自动机(DFA)。
Java - DP
class Solution {
public boolean isFlipedString(String s1, String s2) {
if (s1.length() != s2.length())
return false;
int n = s1.length();
if (n == 0)
return true;
// dp[state][char] = next_state
int[][] dp = new int[n][256];
dp[0][s2.charAt(0)] = 1;
int x = 0; // 影子状态,用于快速计算非匹配转移
for (int s = 1; s < n; s++) {
// 先复制上一状态所有普通字符的转移
for (int c = 0; c < 256; c++)
dp[s][c] = dp[x][c];
// 修正当前字符的特殊转移
dp[s][s2.charAt(s)] = s + 1;
// 更新影子状态
x = dp[x][s2.charAt(s)];
}
String txt = s1 + s1;
int state = 0;
for (int i = 0; i < 2 * n; i++) {
state = dp[state][txt.charAt(i)];
if (state == n)
return true;
}
return false;
}
}C++ - DP
class Solution {
public:
bool isFlipedString(string s1, string s2) {
if (s1.size() != s2.size())
return false;
int n = s1.size();
if (n == 0)
return true;
int dp[n][256];
memset(dp, 0, sizeof(dp));
dp[0][s2[0]] = 1;
int x = 0; // 影子状态
for (int s = 1; s < n; s++) {
for (int c = 0; c < 256; c++)
dp[s][c] = dp[x][c];
dp[s][s2[s]] = s + 1;
x = dp[x][s2[s]];
}
string txt = s1 + s1;
int state = 0;
for (int i = 0; i < 2 * n; i++) {
state = dp[state][txt[i]];
if (state == n)
return true;
}
return false;
}
};- 复杂度分析:预处理构造自动机或 next 表的时间为 O(n × 字符集大小);单次文本扫描时间为 O(n)。总体时间复杂度优于暴力法,空间复杂度为 O(n)(受限于 next 数组或 dp 表)。
调用语言内置 API
在工程实践中,直接利用标准库提供的子串查找功能是最优且最安全的做法。大多数现代语言的标准库实现底层已针对该场景做了高度优化。
Java
class Solution {
public boolean isFlipedString(String s1, String s2) {
return s1.length() == s2.length() && (s1 + s1).contains(s2);
}
}- 时间复杂度:O(n)(取决于底层 JDK 实现,通常为优化的 BM 或混合算法)。
C++
class Solution {
public:
bool isFlipedString(string s1, string s2) {
return s1.size() == s2.size() && (s1 + s1).find(s2) != string::npos;
}
};- 时间复杂度:O(n)。
总结
解决字符串轮转问题时,s1 + s1 包含 s2 是核心的数学直觉。虽然调用内置 API 最为便捷,但掌握 KMP 算法及其变体(如 DP 状态机)能极大加深对模式匹配原理的理解,特别是在面试中对算法复杂度的推演和自定义实现场景下。
以上就是Java C++题解leetcode字符串轮转KMP算法详解的详细内容,更多关于Java C++ 字符串轮转KMP算法的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









