Hard
题目描述
我们定义 str = [s, n] 表示 str 由字符串 s 连接 n 次构成。
- 例如,
str == ["abc", 3] == "abcabcabc"。
我们定义,如果从字符串 s2 中删除某些字符能够得到字符串 s1,那么字符串 s1 可以从字符串 s2 获得。
- 例如,根据定义,
s1 = "abc"可以从s2 = "abdbec"获得,只需要删除加粗且用下划线标出的字符。
现在给你两个字符串 s1 和 s2 和两个整数 n1 和 n2。由此构造得到两个字符串,其中 str1 = [s1, n1]、str2 = [s2, n2]。
请你找出一个最大整数 m,使得 str = [str2, m] 可以从 str1 获得。
示例 1:
输入:s1 = "acb", n1 = 4, s2 = "ab", n2 = 2
输出:2
示例 2:
输入:s1 = "acb", n1 = 1, s2 = "acb", n2 = 1
输出:1
提示:
1 <= s1.length, s2.length <= 100s1和s2由小写英文字母组成1 <= n1, n2 <= 10^6
解题思路
这道题的核心是计算 [s1, n1] 中最多能包含多少个 [s2, n2]。
思路分析
首先需要理解题意:我们有字符串 s1 重复 n1 次形成的长字符串,要在其中找到最多能形成多少个由字符串 s2 重复 n2 次组成的子序列。
方法一:暴力模拟
直接模拟匹配过程,遍历 [s1, n1],尝试匹配 s2,记录能匹配多少个完整的 s2。但这种方法在 n1 很大时会超时。
方法二:循环检测优化(推荐)
由于 s1 和 s2 的长度都不超过 100,当我们遍历多个 s1 时,必然会出现循环模式。我们可以检测这种循环:
- 记录每次遍历完一个
s1后,在s2中的位置 - 当出现相同位置时,说明找到了循环
- 利用循环的性质快速计算结果
具体实现:
- 用数组记录每次遍历完
s1后的状态(在s2中的位置和已匹配的s2个数) - 检测到循环后,计算循环内能匹配多少个
s2 - 最后计算总共能形成多少个
[s2, n2]
时间复杂度优化到 O(|s1| × |s2|),空间复杂度 O(|s2|)。
代码实现
class Solution {
public:
int getMaxRepetitions(string s1, int n1, string s2, int n2) {
if (n1 == 0) return 0;
vector<int> s1_counts(s2.size() + 1, 0); // s1_counts[i] 表示匹配到s2[i]位置时,用了多少个s1
vector<int> s2_counts(s2.size() + 1, 0); // s2_counts[i] 表示匹配到s2[i]位置时,匹配了多少个完整的s2
int s1_count = 0, s2_count = 0;
int s2_index = 0;
while (s1_count < n1) {
s1_count++;
for (char c : s1) {
if (c == s2[s2_index]) {
s2_index++;
if (s2_index == s2.size()) {
s2_count++;
s2_index = 0;
}
}
}
// 检测循环
if (s1_counts[s2_index] > 0) {
// 找到循环
int prev_s1_count = s1_counts[s2_index];
int prev_s2_count = s2_counts[s2_index];
int cycle_s1 = s1_count - prev_s1_count;
int cycle_s2 = s2_count - prev_s2_count;
int remaining_s1 = n1 - s1_count;
int cycle_times = remaining_s1 / cycle_s1;
s2_count += cycle_times * cycle_s2;
s1_count += cycle_times * cycle_s1;
}
s1_counts[s2_index] = s1_count;
s2_counts[s2_index] = s2_count;
}
return s2_count / n2;
}
};
class Solution:
def getMaxRepetitions(self, s1: str, n1: int, s2: str, n2: int) -> int:
if n1 == 0:
return 0
s1_counts = [0] * (len(s2) + 1) # s1_counts[i] 表示匹配到s2[i]位置时,用了多少个s1
s2_counts = [0] * (len(s2) + 1) # s2_counts[i] 表示匹配到s2[i]位置时,匹配了多少个完整的s2
s1_count = s2_count = s2_index = 0
while s1_count < n1:
s1_count += 1
for c in s1:
if c == s2[s2_index]:
s2_index += 1
if s2_index == len(s2):
s2_count += 1
s2_index = 0
# 检测循环
if s1_counts[s2_index] > 0:
# 找到循环
prev_s1_count = s1_counts[s2_index]
prev_s2_count = s2_counts[s2_index]
cycle_s1 = s1_count - prev_s1_count
cycle_s2 = s2_count - prev_s2_count
remaining_s1 = n1 - s1_count
cycle_times = remaining_s1 // cycle_s1
s2_count += cycle_times * cycle_s2
s1_count += cycle_times * cycle_s1
s1_counts[s2_index] = s1_count
s2_counts[s2_index] = s2_count
return s2_count // n2
public class Solution {
public int GetMaxRepetitions(string s1, int n1, string s2, int n2) {
if (n1 == 0) return 0;
int[] s1Counts = new int[s2.Length + 1]; // s1Counts[i] 表示匹配到s2[i]位置时,用了多少个s1
int[] s2Counts = new int[s2.Length + 1]; // s2Counts[i] 表示匹配到s2[i]位置时,匹配了多少个完整的s2
int s1Count = 0, s2Count = 0, s2Index = 0;
while (s1Count < n1) {
s1Count++;
foreach (char c in s1) {
if (c == s2[s2Index]) {
s2Index++;
if (s2Index == s2.Length) {
s2Count++;
s2Index = 0;
}
}
}
// 检测循环
if (s1Counts[s2Index] > 0) {
// 找到循环
int prevS1Count = s1Counts[s2Index];
int prevS2Count = s2Counts[s2Index];
int cycleS1 = s1Count - prevS1Count;
int cycleS2 = s2Count - prevS2Count;
int remainingS1 = n1 - s1Count;
int cycleTimes = remainingS1 / cycleS1;
s2Count += cycleTimes * cycleS2;
s1Count += cycleTimes * cycleS1;
}
s1Counts[s2Index] = s1Count;
s2Counts[s2Index] = s2Count;
}
return s2Count / n2;
}
}
var getMaxRepetitions = function(s1, n1, s2, n2) {
if (n1 === 0) return 0;
let s1Count = 0, s2Count = 0, s2Idx = 0;
let memo = new Map();
while (s1Count < n1) {
for (let i = 0; i < s1.length; i++) {
if (s1[i] === s2[s2Idx]) {
s2Idx++;
if (s2Idx === s2.length) {
s2Count++;
s2Idx = 0;
}
}
}
s1Count++;
if (memo.has(s2Idx)) {
let [prevS1Count, prevS2Count] = memo.get(s2Idx);
let cycleS1 = s1Count - prevS1Count;
let cycleS2 = s2Count - prevS2Count;
let remainingS1 = n1 - s1Count;
let completeCycles = Math.floor(remainingS1 / cycleS1);
s2Count += completeCycles * cycleS2;
s1Count += completeCycles * cycleS1;
} else {
memo.set(s2Idx, [s1Count, s2Count]);
}
}
return Math.floor(s2Count / n2);
};
复杂度分析
| 复杂度类型 | 分析 |
|---|---|
| 时间复杂度 | O(|s1| × |s2|),最坏情况下需要遍历 |s2| 个不同状态,每个状态需要遍历一次 s1 |
| 空间复杂度 | O(|s2|),需要存储每个 s2 位置对应的状态信息 |