686. Repeated String Match*

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]

时, ik 同时向前. 但当它们不相等时, k = dp[k] 进行后退, 只有当 k == 0 即退无可退的时候, ++ i 继续前行. 重复这个过程直到 k == B.size(), 此时在 A (也许重复了多次) 中, 肯定查找到了 B. 这是在 A 中搜索子字符串 B 的基本操作. 然而在这个过程中, 有一些边界条件需要考虑:

  1. 当遍历完一遍 A 之后, B 中找不到任何一个字符和 A 中的字符相等, 即:
if (k == 0 && i >= n) return -1;

此时返回 -1.

  1. 当 A 需要重复自身多次时, 需要更新 count 的大小, 它记录着重复自身的次数:
if (i == n) {
    i %= n;
    count += 1;
}
  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

来自: [C++/Java] Clean Code

基本思路是, 找到 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 of a;

a:"abc" "abc" "abc"
b: "c abc ab" - in this case we need 3 copies of a

We can tell we need at least 1 extra copy, b.size() / a.size() + 1 works fine when b.size() / a.size() == 0
But:
a: "abc" "abc" "abc"
b: "c abc a" - in this case we still need 3 copies of a, but b.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 是不会往回走的.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值