问题描述
由 n 个连接的字符串 s 组成字符串 S,记作 S = [s,n]
。例如,["abc",3]=“abcabcabc”
。
如果我们可以从 s2 中删除某些字符使其变为 s1,则称字符串 s1 可以从字符串 s2 获得。例如,根据定义,“abc” 可以从 “abdbec” 获得,但不能从 “acbbe” 获得。
现在给你两个非空字符串 s1 和 s2(每个最多 100 个字符长)和两个整数 0 ≤ n1 ≤ 106 和 1 ≤ n2 ≤ 106。现在考虑字符串 S1 和 S2,其中 S1=[s1,n1]
、S2=[s2,n2]
。
请你找出一个可以满足使 [S2,M]
从 S1
获得的最大整数 M 。
解题报告
\qquad 如果s2在S1中出现了N次,那么S2肯定在S1中出现了 N / n 2 N/ n_2 N/n2 次。
\qquad
直接暴力求解s2
在S1
中出现的次数显然不合理。很明显,只要有解,且S1的长度足够长,那么S1中必然会存在s2的循环节。
\qquad
问题的关键就变成如何求解这个循环节。
\qquad
解决方式是:计算 S1
中每个小段第一次和 s2
中哪个字符发生匹配。
\qquad
比如:s1=“abc”
,s2=“bac”
。则S1
的第一段第一次与s2
中的字符发生匹配的是第一个字符,记录mp[0]=1
;S1
的第二段第一次与s2
中的字符发生匹配的是第二个字符,记录mp[1]=2
;S1
的第三段第一次与s2
中的字符发生匹配的是第一个字符,此时mp[0]
已经有值,所以循环节出现了。
\qquad
为什么此时循环节出现了呢,因为一旦mp[i]
有值了【只要能进行匹配,那么此时一次若干次完整的匹配已经完成了】,由于S1
是小段循环的,所以S1
接下来的段都会按照mp[i]
第一次赋值时那样的匹配方式来进行,这样就不必继续计算了。
\qquad
所以代码的核心思想是:计算这个pattern占了S1
的多少段,S1
一共有多少个pattern
,最后计算剩下的非pattern
的字符串里能包含几个s2
,最后将两者相加除以
n
2
n_2
n2 即为最终的答案了。
\qquad
时间复杂度为:
O
(
s
1.
s
i
z
e
(
)
∗
s
2.
s
i
z
e
(
)
)
O(s1.size()*s2.size())
O(s1.size()∗s2.size())。虽然代码看着时间复杂度为
O
(
n
1
∗
s
2.
s
i
z
e
(
)
)
O(n_1*s2.size())
O(n1∗s2.size())。实际上,因为S1
的每一段对应着s2
的某一位字符,那么在进行s2.size()
段分析之后,会结束循环。根据鸽巢原理(又称抽屉原理),在进行s2.size()+1
段之后,必定有s2
的某一位字符重复出现了。
\qquad
空间复杂度为:
O
(
s
2.
s
i
z
e
(
)
)
O(s2.size())
O(s2.size())。
实现代码
class Solution {
public:
int getMaxRepetitions(string s1, int n1, string s2, int n2) {
vector<int> repeatCnt(s2.size() + 2, 0);
unordered_map<int,int>mp;
int j = 0, cnt = 0;
for (int k = 1; k <= n1; ++k) {
for (int i = 0; i < s1.size(); ++i) {
if (s1[i] == s2[j]) {
++j;
if (j == s2.size()) {
j = 0;
++cnt;
}
}
}
repeatCnt[k] = cnt;
if(mp.count(j)){
int start=mp[j];
int interval = k - start;
int repeat = (n1 - start) / interval;
int patternCnt = (repeatCnt[k] - repeatCnt[start]) * repeat;
int remainCnt = repeatCnt[start + (n1 - start) % interval];
return (patternCnt + remainCnt) / n2;
}
else
mp[j]=k;
//if(k>s2.size()) break;
}
return cnt / n2;
}
};
参考资料
[1] Leetcode 466. 统计重复个数
[2] [LeetCode] 466. Count The Repetitions 计数重复个数