递归删除子串的出现

时间:2015-02-21 06:43:51

标签: string algorithm substring

这是一个问题:

  

给定字符串A和子字符串B,删除字符串A中第一个出现的子字符串B,直到可以这样做。请注意,删除子字符串可以进一步创建新的相同子字符串。防爆。从'hehelllloworld'中移除'地狱'一旦产生'helloworld',再次移除之后将成为'oworld',即所需的字符串。

为A编写一个程序,输入约束长度为10 ^ 6,B为长度。

我在一次采访中向我询问了这个问题,我给了他们一个简单的算法来解决这个问题,这个算法完全符合声明的意思并将其删除(以降低头部调用次数),我后来才发现它有一个更好的解决方案,它会更快,它会是什么?我已经考虑过一些优化,但它仍然没有解决问题的最快速度(根据公司),所以有人能告诉我更快的方法来解决问题吗?

P.S>我知道stackoverflow规则和代码更好,但对于这个问题,我不认为有代码会以任何方式有益...

1 个答案:

答案 0 :(得分:3)

你的方法复杂性很差。在非常糟糕的情况下,字符串a将为aaaaaaaaabbbbbbbbb,字符串b将为ab,在这种情况下,您需要O(|a|)次搜索,每次搜索取O(|a| + |b|)(假设使用一些复杂的搜索算法),导致O(|a|^2 + |a| * |b|)的总复杂度,其约束条件为年。

对于他们的约束,一个很好的复杂目标是O(|a| * |b|),即大约1亿次操作,将在亚秒内完成。这是接近它的一种方法。对于字符串i中的每个位置a,我们计算最大长度n_i,以便a[i - n_i : i] = b[0 : n_i](换句话说,a的最长后缀b 1}}在该位置是O(|a| + |b|)的前缀。我们可以使用Knuth-Morris-Pratt算法在n_i中计算它。

我们计算b后,在a中找到n_i的第一个匹配项只是找到等于|b|的第一个b }。这将是a中出现b之一的正确结尾。

最后,我们需要稍微修改Knuth-Morris-Pratt。一旦我们计算出等于n_i的{​​{1}},我们就会在逻辑上删除|b|的出现次数。为了解释某些字母已从a中移除的事实,我们将依赖于Knuth-Morris-Pratt仅依赖于n_i的最后一个值(以及为b计算的那些值的事实),以及a的当前字母,因此我们只需要在逻辑删除n_i的出现后快速检索b的最后一个值。这可以通过deque来完成,deque存储n_i的所有有效值。每个值都会被推入deque一次,并从中弹出一次,因此维护它的复杂性为O(|a|),而Knuth-Morris-Pratt的复杂性为O(|a| + |b|),导致{O(|a| + |b|)总复杂性。

这是一个C ++实现。它可能会有一些一对一的错误,但它适用于您的样本,并且它会在我开头描述的最坏情况下飞行。

#include <deque>
#include <string>
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    string a, b;
    cin >> a >> b;

    size_t blen = b.size();

    // make a = b$a
    a = b + "$" + a;

    vector<size_t> n(a.size()); // array for knuth-morris-pratt
    vector<bool> removals(a.size()); // positions of right ends at which we remove `b`s

    deque<size_t> lastN;
    n[0] = 0;

    // For the first blen + 1 iterations just do vanilla knuth-morris-pratt
    for (size_t i = 1; i < blen + 1; ++ i) {
        size_t z = n[i - 1];
        while (z && a[i] != a[z]) {
            z = n[z - 1];
        }
        if (a[i] != a[z]) n[i] = 0;
        else n[i] = z + 1;

        lastN.push_back(n[i]);
    }

    // For the remaining iterations some characters could have been logically
    //     removed from `a`, so use lastN to get last value of n instaed
    //     of actually getting it from `n[i - 1]`
    for (size_t i = blen + 1; i < a.size(); ++ i) {
        size_t z = lastN.back();
        while (z && a[i] != a[z]) {
            z = n[z - 1];
        }
        if (a[i] != a[z]) n[i] = 0;
        else n[i] = z + 1;

        if (n[i] == blen) // found a match
        {
            removals[i] = true;

            // kill last |b| - 1 `n_i`s
            for (size_t j = 0; j < blen - 1; ++ j) {
                lastN.pop_back();
            }
        }
        else {
            lastN.push_back(n[i]);
        }
    }

    string ret;
    size_t toRemove = 0;
    for (size_t pos = a.size() - 1; a[pos] != '$'; -- pos) {
        if (removals[pos]) toRemove += blen;
        if (toRemove) -- toRemove;
        else ret.push_back(a[pos]);
    }
    reverse(ret.begin(), ret.end());

    cout << ret << endl;

    return 0;
}
[in] hehelllloworld
[in] hell
[out] oworld
[in] abababc
[in] ababc
[out] ab
[in] caaaaa ... aaaaaabbbbbb ... bbbbc
[in] ab
[out] cc