优化所有子串的构造

时间:2015-07-01 16:11:19

标签: algorithm optimization data-structures trie suffix-tree

我正在解决一个与trie相关的问题。有一组字符串 S 。我必须为 S 中的每个字符串的所有子字符串创建一个trie。我使用以下例程:

String strings[] = { ... }; // array containing all strings
for(int i = 0; i < strings.length; i++) {
    String w = strings[i];
    for (int j = 0; j < w.length(); j++) {
        for (int k = j + 1; k <= w.length(); k++) {
            trie.insert(w.substring(j, k));
        }
    }
}

我正在使用here提供的trie实现。但是,我想知道是否可以进行某些优化以降低在所有子串上创建trie的复杂性?

为什么我需要这个?因为我正在尝试解决this problem

4 个答案:

答案 0 :(得分:2)

如果我们有N个单词,每个单词的最大长度为L,那么您的算法将采用O(N*L^3)(假设添加到trie与添加单词的长度呈线性关系)。但是,生成的trie(节点数)的大小最多为O(N*L^2),因此您似乎在浪费时间并且可以做得更好。

事实上你可以,但你必须从你的袖子中汲取一些技巧。此外,您将不再需要trie。

  1. .substring()在恒定时间内
  2. 在Java 7中,每个String都有一个支持char[]数组以及起始位置和长度。这允许.substring()方法在恒定时间内运行,因为String是不可变类。创建了具有相同后备String数组的新char[]对象,仅具有不同的起始位置和长度。

    您需要稍微扩展一下,以支持在字符串末尾添加,增加长度。始终创建一个新的字符串对象,但保持后缀数组相同。

    1. 在追加单个字符
    2. 后的固定时间内重新计算哈希值

      再次,让我使用Java hashCode()函数String

      int hash = 0;
      for (int i = 0; i < data.length; i++) {
          hash = 31 * hash + data[i];
      } // data is the backing array
      

      现在,在单词末尾添加单个字符后,哈希会如何变化?简单,只需添加它的值(ASCII码)乘以31^length。你可以在一个单独的表中保持31的幂,也可以使用其他素数。

      1. 将所有子字符串存储在单个HashMap
      2. 使用技巧1和2,您可以生成时间O(N*L^2)中的所有子字符串,这是子字符串的总数。只需始终以长度为1的字符串开头,一次添加一个字符。将所有字符串放入单个HashMap中,以减少重复。

        (您可以跳过2和3并在排序时/之后丢弃重复项,也许它会更快。)

        1. 排序你的子串,你很高兴。
        2. 好吧,当我到达第4点时,我意识到我的计划不会起作用,因为在排序时你需要比较字符串,这可能需要O(L)时间。我想出了几次解决它的尝试,其中包括桶分类,但没有一个会比原来O(N*L^3)更快

          我会在这里给出这个答案,以防它激励某人。

          如果您不了解Aho-Corasic algorithm,请仔细研究一下,它可能会对您的问题有所帮助。

答案 1 :(得分:2)

您需要的可能是suffix automaton。它只花费O(n)时间并且可以识别所有子串。

Suffix array也可以解决这个问题。

这两种算法可以解决大多数字符串问题,而且它们很难学习。学完之后你就会解决它。

答案 2 :(得分:1)

您可以考虑以下优化:

  • 维护已处理子字符串的列表。在插入子字符串时,检查已处理的集合是否包含该特定子字符串,如果是,则跳过在该集群中插入该子字符串。

然而,在trie中插入所有子串的最坏情况复杂度将是n ^ 2的量级,其中n是字符串数组的大小。从问题页面,这可以是trie中10 ^ 8个插入操作的顺序。因此,即使每次插入平均需要10次操作,总共会有10 ^ 9次操作,这会使您超过时间限制。

问题页面将LCP阵列称为问题的相关主题。你应该考虑改变方法。

答案 3 :(得分:1)

首先,请注意只添加后缀到trie就足够了,并且每个子字符串的节点都会被添加到路径中。

其次,你必须compress the trie,否则它将不适合HackerRank强加的内存限制。这也将使您的解决方案更快。

我刚刚提交了实施这些建议的解决方案,并was accepted。 (最长执行时间为0.08秒。)

但是,您可以通过实施线性时间算法来构建suffix tree,从而使解决方案更快。您可以阅读线性时间后缀树构造算法herehere。在StackOverflow here上还有对Ukkonen算法的解释。