【问题标题】:how to find distinct substrings?如何找到不同的子字符串?
【发布时间】:2015-06-13 13:11:43
【问题描述】:

给定一个字符串和一个固定长度 l,我如何计算长度为 l 的不同子字符串的数量? 字符集的大小也是已知的。 (记为 s) 例如,给定一个字符串“PccjcjcZ”,s = 4,l = 3, 那么有5个不同的子字符串: “个人计算机”; “ccj”; “cjc”; “jcj”; “jcZ”

我尝试使用哈希表,但速度仍然很慢。 实际上我不知道如何使用字符大小。 我做过这样的事情

int diffPatterns(const string& src, int len, int setSize) {
  int cnt = 0;
  node* table[1 << 15];
  int tableSize = 1 << 15;
  for (int i = 0; i < tableSize; ++i) {
    table[i] = NULL;
  }

  unsigned int hashValue = 0;

  int end = (int)src.size() - len;

  for (int i = 0; i <= end; ++i) {
    hashValue = hashF(src, i, len);
    if (table[hashValue] == NULL) {
      table[hashValue] = new node(i);
      cnt ++;
    } else {
      if (!compList(src, i, table[hashValue], len)) {
        cnt ++;
      };
    }
  }

  for (int i = 0; i < tableSize; ++i) {
    deleteList(table[i]);
  }

  return cnt;
}

【问题讨论】:

  • 你真的需要找到所有的子字符串还是只需要它们的数量?
  • 你的代码有什么问题?你只想让它更快?
  • @NathanOliver 只是数字
  • @tobi303 更快
  • 如果这段代码大部分时间都在用2^15 元素数组填充NULLs,我不会感到惊讶。只需使用unordered_set&lt;string&gt; 或类似名称。

标签: c++ algorithm hash


【解决方案1】:

使用哈希表的想法很好。它应该运作良好。

将自己的哈希表实现为长度为 2^15 的数组的想法很糟糕。请参阅Hashtable in C++?

【讨论】:

    【解决方案2】:

    您可以使用unorder_set 并将字符串插入集合中,然后获取集合的大小。由于集合中的值是唯一的,因此它会注意不包括与先前找到的相同的子字符串。这应该让您接近 O(StringSize - SubstringSize) 复杂度

    #include <iostream>
    #include <string>
    #include <unordered_set>
    
    
    int main()
    {
        std::string test = "PccjcjcZ";
        std::unordered_set<std::string> counter;
        size_t substringSize = 3;
        for (size_t i = 0; i < test.size() - substringSize + 1; ++i)
        {
            counter.insert(test.substr(i, substringSize));
        }
    
        std::cout << counter.size();
    
        std::cin.get();
        return 0;
    }
    

    【讨论】:

      【解决方案3】:

      Hastables 很好也很实用,但是请记住,如果子串的长度是 L,而整个字符串的长度是 N,那么算法就是 Theta((N+1-L)*L),即 Theta( NL) 对于大多数 L。请记住,仅计算散列需要 Theta(L) 时间。另外可能会有碰撞。

      可以使用后缀树,并提供有保证的 O(N) 时间算法(计算深度 L 或更大的路径数),但实现起来很复杂。可取之处在于,您可能可以使用您选择的语言找到现成的实现。

      【讨论】:

        【解决方案4】:

        Veronica Kham 对这个问题的回答很好,但我们可以将此方法改进为预期的 O(n),并且仍然使用简单的哈希表而不是后缀树或任何其他高级数据结构。

        哈希函数

        XY 是长度为L 的两个相邻子串,更准确地说:

        X = A[i, i + L - 1]

        Y = B[i + 1, i + 1 + L - 1]

        让我们为字母表中的每个字母分配一个非负整数,例如a := 1, b := 2 等等。

        现在让我们定义一个哈希函数h

        h(A[i, j]) := (P^(L-1) * A[i] + P^(L-2) * A[i + 1] + ... + A[j]) % M

        其中P 是一个理想地大于字母大小的素数,M 是一个非常大的数字,表示不同的可能散列的数量,例如,您可以将M 设置为最大可用unsigned long long int系统。

        算法

        关键的观察结果如下:

        如果您计算了 X 的哈希值,您可以计算 Y 的哈希值 O(1)时间。

        假设我们已经计算了h(X),这显然可以在O(L) 时间内完成。我们要计算h(Y)。请注意,由于 XY 仅相差 2 个字符,我们可以使用加法和乘法轻松做到这一点:

        h(Y) = ((h(X) - P^L * A[i]) * P) + A[j + 1]) % M

        基本上,我们减去字母 A[i] 乘以它在 h(X) 中的系数,然后将结果乘以 P 以获得其余字母的适当系数,最后,我们添加最后一个信A[j + 1]

        请注意,我们可以在开头预先计算 P 的幂,我们可以对 M 取模。

        由于我们的散列函数返回整数,我们可以使用任何散列表来存储它们。请记住使所有计算都以 M 为模并避免整数溢出。

        碰撞

        当然,可能会发生碰撞,但由于P 是素数,而M 确实很大,所以这种情况很少见。

        如果您想降低发生冲突的概率,您可以使用两个不同的散列函数,例如在每个函数中使用不同的模数。如果使用一个这样的函数发生碰撞的概率是p,那么对于两个函数来说它是p^2,我们可以通过这个技巧将其任意小。

        【讨论】:

          【解决方案5】:

          使用Rolling hashes

          这将使运行时预期为 O(n)。

          这可能是重复 pkacprzak 的答案,除了它给出了一个更容易记忆的名称等。

          【讨论】:

            【解决方案6】:

            后缀自动机也可以在 O(N) 内完成。

            编码很容易,但很难理解。

            这里有关于它的论文http://dl.acm.org/citation.cfm?doid=375360.375365

            http://www.sciencedirect.com/science/article/pii/S0304397509002370

            【讨论】:

              猜你喜欢
              • 2019-11-19
              • 2011-07-06
              • 2021-11-09
              • 1970-01-01
              • 1970-01-01
              • 2012-10-24
              • 1970-01-01
              • 2012-08-20
              • 2022-01-14
              相关资源
              最近更新 更多