【问题标题】:Remove occurrences of substring recursively递归删除子字符串的出现
【发布时间】:2015-04-22 23:02:08
【问题描述】:

这里有个问题:

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

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

在一次采访中向我提出了这个问题,我给了他们一个简单的算法来解决它,即完全按照语句的内容进行操作,然后迭代地删除它(以减少头顶调用),后来我才知道有一个更快的更好的解决方案是什么?我已经想到了一些优化,但它仍然没有解决问题的最快方法(acc. the company)那么快,所以谁能告诉我更快的方法来解决这个问题?

P.S> 我知道 stackoverflow 规则并且有代码更好,但是对于这个问题,我认为有代码不会有任何好处......

【问题讨论】:

  • 如果你没有发布你的代码,很难知道我们的代码是否比你的更快。
  • @cphlewis,我们当然可以。他解释了他的方法,从那个解释中可以看出它的复杂性。你要么建议一个复杂度更高的算法,要么不建议。
  • @cphlewis 测试代码是评估算法性能的一种相当有限的方法,因为 (a) 你不可能涵盖所有可能的输入,并且 (b) 由于实现细节,总会存在恒定的因素差异。

标签: string algorithm substring


【解决方案1】:

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

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

在我们计算出n_i 之后,在a 中找到b 的第一个匹配项只需找到第一个等于|b|n_i。这将是ab 出现之一的右端。

最后,我们需要稍微修改一下 Knuth-Morris-Pratt。一旦我们计算出等于|b|n_i,我们就会在逻辑上删除b 的出现。考虑到从a 中删除了一些字母这一事实,我们将依赖这样一个事实,即 Knuth-Morris-Pratt 仅依赖于 n_i 的最后一个值(以及为 b 计算的值)和当前字母a,所以我们只需要一种快速的方法来检索 n_i 的最后一个值,然后我们在逻辑上删除 b 的出现。这可以通过存储n_i 的所有有效值的双端队列来完成。每个值将被推入双端队列一次,并从中弹出一次,因此维护它的复杂度为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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-09
    • 2016-07-04
    • 2020-08-12
    • 2013-02-05
    • 1970-01-01
    • 2014-08-07
    • 1970-01-01
    • 2019-04-02
    相关资源
    最近更新 更多