我正在解决一个与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。
答案 0 :(得分:2)
如果我们有N
个单词,每个单词的最大长度为L
,那么您的算法将采用O(N*L^3)
(假设添加到trie与添加单词的长度呈线性关系)。但是,生成的trie(节点数)的大小最多为O(N*L^2)
,因此您似乎在浪费时间并且可以做得更好。
事实上你可以,但你必须从你的袖子中汲取一些技巧。此外,您将不再需要trie。
.substring()
在恒定时间内在Java 7中,每个String
都有一个支持char[]
数组以及起始位置和长度。这允许.substring()
方法在恒定时间内运行,因为String
是不可变类。创建了具有相同后备String
数组的新char[]
对象,仅具有不同的起始位置和长度。
您需要稍微扩展一下,以支持在字符串末尾添加,增加长度。始终创建一个新的字符串对象,但保持后缀数组相同。
再次,让我使用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的幂,也可以使用其他素数。
HashMap
使用技巧1和2,您可以生成时间O(N*L^2)
中的所有子字符串,这是子字符串的总数。只需始终以长度为1的字符串开头,一次添加一个字符。将所有字符串放入单个HashMap中,以减少重复。
(您可以跳过2和3并在排序时/之后丢弃重复项,也许它会更快。)
好吧,当我到达第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,从而使解决方案更快。您可以阅读线性时间后缀树构造算法here和here。在StackOverflow here上还有对Ukkonen算法的解释。