Medium
题目描述
给你一个下标从 0 开始的字符串 word 和一个整数 k。
在每一秒,你必须执行以下操作:
- 移除
word的前k个字符。 - 在
word的末尾添加k个字符。
注意,添加的字符不必和你移除的字符相同。但是,你必须在每一秒都执行这两个操作。
返回 word 恢复到初始状态所需的最短时间(大于 0)。
示例 1:
输入:word = "abacaba", k = 3
输出:2
解释:第 1 秒,我们移除 word 的前缀 "aba",并在末尾添加 "bac"。因此 word 变为 "cababac"。
第 2 秒,我们移除 word 的前缀 "cab",并在末尾添加 "aba"。因此 word 变为 "abacaba" 并恢复到初始状态。
可以证明,2 秒是 word 恢复到初始状态所需的最短时间(大于 0)。
示例 2:
输入:word = "abacaba", k = 4
输出:1
解释:第 1 秒,我们移除 word 的前缀 "abac",并在末尾添加 "caba"。因此 word 变为 "abacaba" 并恢复到初始状态。
可以证明,1 秒是 word 恢复到初始状态所需的最短时间(大于 0)。
示例 3:
输入:word = "abcbabcd", k = 2
输出:4
解释:每一秒,我们都移除 word 的前 2 个字符,并在末尾添加相同的字符。
4 秒后,word 变为 "abcbabcd" 并恢复到初始状态。
可以证明,4 秒是 word 恢复到初始状态所需的最短时间(大于 0)。
提示:
1 <= word.length <= 501 <= k <= word.lengthword仅由小写英文字母组成。
提示:
- 找到既是后缀又是前缀且长度是
k的倍数的最长字符串。
解题思路
解题思路
这道题的核心是理解每次操作后字符串的变化规律。每次移除前 k 个字符相当于字符串左移 k 位,而添加的字符可以任意选择,所以关键是找到合适的添加策略使字符串尽快恢复原状。
观察发现,如果我们希望在第 t 次操作后恢复到原始字符串,那么经过 t 次操作后,原字符串的后 n - t*k 个字符(其中 n 是字符串长度)应该与原字符串的前 n - t*k 个字符相同。换句话说,我们需要找到一个长度为 n - t*k 的字符串,它既是原字符串的前缀又是后缀。
具体算法:
- 枚举操作次数
t从 1 开始 - 对于每个
t,计算剩余长度remain = n - t*k - 如果
remain <= 0,说明字符串已经完全被移除,此时可以通过添加原字符串来恢复,返回t - 否则检查原字符串的前
remain个字符是否等于后remain个字符 - 如果相等,说明可以在第
t次操作后恢复,返回t
这个方法的时间复杂度是 O(n²/k),在给定约束下完全可以接受。
另一种优化思路是使用 KMP 算法或 Z 算法预处理前缀后缀匹配信息,但在这个题目的数据规模下,朴素方法已经足够高效。
代码实现
class Solution {
public:
int minimumTimeToInitialState(string word, int k) {
int n = word.length();
for (int t = 1; ; t++) {
int remain = n - t * k;
if (remain <= 0) {
return t;
}
// 检查前remain个字符是否等于后remain个字符
bool match = true;
for (int i = 0; i < remain; i++) {
if (word[i] != word[n - remain + i]) {
match = false;
break;
}
}
if (match) {
return t;
}
}
}
};
class Solution:
def minimumTimeToInitialState(self, word: str, k: int) -> int:
n = len(word)
t = 1
while True:
remain = n - t * k
if remain <= 0:
return t
# 检查前remain个字符是否等于后remain个字符
if word[:remain] == word[n-remain:]:
return t
t += 1
public class Solution {
public int MinimumTimeToInitialState(string word, int k) {
int n = word.Length;
for (int t = 1; ; t++) {
int remain = n - t * k;
if (remain <= 0) {
return t;
}
// 检查前remain个字符是否等于后remain个字符
bool match = true;
for (int i = 0; i < remain; i++) {
if (word[i] != word[n - remain + i]) {
match = false;
break;
}
}
if (match) {
return t;
}
}
}
}
var minimumTimeToInitialState = function(word, k) {
const n = word.length;
for (let t = 1; t * k < n; t++) {
const removed = t * k;
if (word.substring(removed) === word.substring(0, n - removed)) {
return t;
}
}
return Math.ceil(n / k);
};
复杂度分析
| 复杂度 | 大小 |
|---|---|
| 时间复杂度 | O(n²/k) |
| 空间复杂度 | O(1) |
说明:
- 时间复杂度:外层循环最多执行 ⌈n/k⌉ 次,内层字符串比较最多需要 O(n) 时间,总体为 O(n²/k)
- 空间复杂度:只使用了常数额外空间
相关题目
- . Longest Happy Prefix (Hard)