686. Repeated String Match*
https://leetcode.com/problems/repeated-string-match/
题目描述
Given two strings A
and B
, find the minimum number of times A
has to be repeated such that B
is a substring of it. If no such solution, return -1.
For example, with A = "abcd"
and B = "cdabcdab"
.
Return 3, because by repeating A
three times ("abcdabcdabcd"
), B
is a substring of it; and B
is not a substring of A
repeated two times ("abcdabcd"
).
Note:
The length of A
and B
will be between 1 and 10000.
C++ 实现 1
C++ 实现 2
没有使用 KMP 算法, 思路更为简洁. 但是我一开始想到的是 KMP, 所以考虑将实现思路写出来作为参考.
这道题使用 KMP 算法的思路来做. 关于 KMP 算法, 可以参看我的博客:
之所以会想到 KMP, 是因为这道题本身可以理解为在字符串中查找某个 pattern (或者说某个子字符串), 效果相当于 28. Implement strStr()*. 这说明, 不仅可以利用 std::find
来处理 (比如 C++ 实现 2
), 也可以使用 KMP 的思路来考虑. 我一开始想到的公式是:
A[i % A.size()] = B[k]
A 重复自身多次形成一个新的字符串, 在新的字符串中查找子字符串 B.
在下面代码实现中, KMP 算法的实现可以参照我原来写的博客. 之后使用 count
来记录需要 A 重复多少次. 使用 KMP 的时候需要谨记: 指针 i
始终是向前的 (虽然这题中 i
需要求余, 但本质上仍是向前), 而 k
是有可能后退的. 当满足:
A[i % n] == B[k]
时, i
和 k
同时向前. 但当它们不相等时, k = dp[k]
进行后退, 只有当 k == 0
即退无可退的时候, ++ i
继续前行. 重复这个过程直到 k == B.size()
, 此时在 A
(也许重复了多次) 中, 肯定查找到了 B
. 这是在 A
中搜索子字符串 B
的基本操作. 然而在这个过程中, 有一些边界条件需要考虑:
- 当遍历完一遍 A 之后, B 中找不到任何一个字符和 A 中的字符相等, 即:
if (k == 0 && i >= n) return -1;
此时返回 -1
.
- 当 A 需要重复自身多次时, 需要更新
count
的大小, 它记录着重复自身的次数:
if (i == n) {
i %= n;
count += 1;
}
- 第一条考虑了第一轮遍历时, 一定要在 A 中找到字符和 B 中的某个字符相等. 然后可能 A 会被重复多次, 在重复多次的过程中, k 可能也会因为:
A[i % n] != B[k]
而被回退多次. 如果出现这样的情况, 那么循环将永远不会结束 (即 k < B.size()
可能永远不会成立). 因此, A 的重复次数不会是无限多次的, 即要限制 A 重复的次数. 这个条件我一开始没有找出, 直到看到:
参考资料: [C++/Java] Clean Code
才明白, A 最多重复 B.size() / A.size() + 2
次, 具体原因见上面的资料或者 C++ 实现 2
.
所以第三个限制条件是:
if (count > (B.size() / A.size() + 2)) return -1;
最终代码如下: (这不是一道 easy 题…)
class Solution {
private:
vector<int> KMP(const string &s) {
vector<int> dp(s.size() + 1, 0);
int j = 0, i = 1;
while (i < s.size()) {
if (s[i] == s[j]) dp[++i] = ++j;
else {
if (j == 0) ++ i;
else j = dp[j];
}
}
return dp;
}
public:
int repeatedStringMatch(string A, string B) {
int i = 0, k = 0;
int count = 1;
int n = A.size();
auto dp = KMP(B);
while (k < B.size()) {
if (i == n) {
i %= n;
count += 1;
}
if (A[i % n] == B[k]) ++ i, ++ k;
else {
if (count > (B.size() / A.size() + 2)) return -1;
if (k == 0) ++ i;
else k = dp[k];
}
// 遍历完一遍 A 之后, B 中找不到任何一个字符和 A 中的字符相等
if (k == 0 && i >= n) return -1;
}
return count;
}
};
C++ 实现 2
基本思路是, 找到 A 重复的最大次数, 然后不断重复 A 已构成新的字符串 S, 然后在 S 中查找 B.
first of all, we need as to be at least as long as
b
:
a: "abc" "abc"
b: "abc abc"
- in this case we need 2 copies ofa
;
a:"abc" "abc" "abc"
b: "c abc ab"
- in this case we need 3 copies of aWe can tell we need at least 1 extra copy,
b.size() / a.size() + 1
works fine whenb.size() / a.size() == 0
But:
a: "abc" "abc" "abc"
b: "c abc a"
- in this case we still need 3 copies ofa
, butb.size() / a.size() (5/3)
only give you 1
That’s why we want to add at least 2 copies.
因此, A 最多重复 B.size() / A.size() + 2
次.
class Solution {
public:
int repeatedStringMatch(string A, string B) {
string S = A;
for (int rep = 1; rep <= B.size() / A.size() + 2; rep++, S += A)
if (S.find(B) != string::npos) return rep;
return -1;
}
};
C++ 实现 1
比这个要更快一些. 因为 S.find(B)
这一步, 内部的 i
会往回走, 但是使用 KMP 算法 i
是不会往回走的.