【问题标题】:more efficient method of substring calculation for advice建议的子串计算更有效的方法
【发布时间】:2016-02-17 06:12:41
【问题描述】:

我的代码有效,我正在寻找更聪明的想法以提高效率?

对于字符串相似度,定义为最长公共前缀长度, 例如,“abc”和“abd”为 2,“aaa”和“aaab”为 3。

问题是计算字符串 S 及其所有后缀的相似度, 包括它自己作为第一个后缀。

例如,对于 S="ababaa",后缀为 "ababaa"、"babaa"、"abaa"、"baa"、"aa" 和“a”,相似度为6+0+3+0+1+1=11

# Complete the function below.
from collections import defaultdict

class TrieNode:
    def __init__(self):
        self.children=defaultdict(TrieNode)
        self.isEnd=False
class TrieTree:
    def __init__(self):
        self.root=TrieNode()
    def insert(self, word):
        node = self.root
        for w in word:
            node = node.children[w]
        node.isEnd = True
    def search(self, word):
        node = self.root
        count = 0
        for w in word:
            node = node.children.get(w)
            if not node:
                break
            else:
                count += 1
        return count

def  StringSimilarity(inputs):
    resultFormat=[]
    for word in inputs:
        # build Trie tree
        index = TrieTree()
        index.insert(word)
        result = 0
        # search for suffix
        for i in range(len(word)):
            result += index.search(word[i:])
        print result
        resultFormat.append(result)

    return resultFormat

【问题讨论】:

  • 我觉得你的问题更适合codereview.stackexchange.com
  • Andrea 是对的,不过,我建议this
  • @Rockybilly,我认为编辑距离与我的要求不同?我只需要检查前缀。谢谢。
  • 我建议您稍微更改措辞以使用“子字符串”而不是“后缀”,因为后缀仅表示单词的开头,而子字符串表示可以显示的字符串的较小部分字符串中的任意位置
  • @Rockybilly - 我认为这只是一个带有人为字符串相似性的编码挑战:hackerrank

标签: python algorithm


【解决方案1】:
def similarity(s, t):
    """ assumes len(t) <= len(s), which is easily doable"""
    i = 0
    while i < len(t) and s[i] == t[i]:
        i += 1
    return i

def selfSimilarity(s):
    return sum(similarity(s, s[i:]) for i in range(len(s)))

selfSimilarity("ababaa")
# 11

【讨论】:

  • 谢谢dwanderson,为什么你的方法比我的Trie树快? :)
  • 我实际上没有机会使用你的方法,所以我不肯定。一个潜在的问题只是开销——而不是使用str,这是一个内置的并且非常轻量级;每个后缀的每个字母都是一个TrieNode,它有一个defaultdict,它有一个keyvalue,这是另一个TrieNode...这是很多对象实例化和创建(因此是取消引用) , 而不是一个字母一个字母的 strcmp。正如@btilly 提到的那样——你没有重复使用任何TrieNodes,所以直接测试更简单、更快捷。
  • 显然是实际有效的实现(可能是@Peter di Rivaz 的建议,尽管那里的信息非常缺乏):this lecture
  • 感谢 dwanderson,我阅读了参考资料并进行了更多分析,特别是有一点感到困惑。我认为可能还有另一个 l2,其中值 z[i-l2] 大于 z[i-l],它甚至可以跳过更多要比较的元素,而子字符串 [l2...r2] 不一定是作为子字符串 [l...r] 最匹配的段。任何 cmets 表示赞赏。
  • @LinMa 希望我能帮上忙,但我自己并不知道Z algorithm;我刚刚找到了链接,看起来很相关:)。
【解决方案2】:

您不妨考虑以下 3 种有效方法:

后缀树

计算原始字符串的后缀树。然后通过后缀树沿着主路径下降,计算每个阶段有多少路径离开主路径。

后缀数组

计算后缀数组和最长公共前缀数组。 这些数组可用于计算任何一对后缀的最长前缀,尤其是原始字符串和每个后缀之间的最长前缀。

Z 函数

您尝试构建的输出称为 Z 函数。 它可以直接以线性时间计算,如here 所示(显然不是 Python 代码):

vector z_function(string s) {
    int n = (int) s.length();
    vector z(n);
    for (int i = 1, l = 0, r = 0; i < n; ++i) {
        if (i <= r)
            z[i] = min (r - i + 1, z[i - l]);
        while (i + z[i] < n && s[z[i]] == s[i + z[i]])
            ++z[i];
        if (i + z[i] - 1 > r)
            l = i, r = i + z[i] - 1;
    }
    return z;
}

【讨论】:

  • 谢谢彼得。有兴趣探索您的所有建议。对于您对后缀数组的建议——“计算后缀数组和最长公共前缀数组”,这里的数组到底是什么意思?例如,在我的输入字符串“ababaa”中,数组是什么?
  • 后缀数组是提供每个后缀的起始位置的数组,参见here。在实践中,后缀数组通常可用于执行与后缀树相同类型的任务,但它们通常更快。
  • 谢谢彼得,我花时间研究你提供的 Z 函数 twiki,我对这个陈述感到困惑,“为此,我们观察到子字符串 s[l…r] 和 s[0… r−l]match。这意味着作为 z[i] 的初始近似值,我们可以采用已经为相应段 s[0…r−l] 计算的值,即 z[i−l]。",我的困惑是我们在计算段 s[0…r−l] 时得到的信息是如何使用的?感谢更多的cmets。谢谢。
  • 和彼得,做了更多的分析,特别是有一点困惑。我认为可能还有另一个 l2,其中值 z[i-l2] 大于 z[i-l],它甚至可以跳过更多要比较的元素,而子字符串 [l2...r2] 不一定是作为子字符串 [l...r] 最匹配的段。任何 cmets 表示赞赏。
  • 感谢彼得的所有帮助,将您的回复标记为已回答。
【解决方案3】:

构建TrieTree 对象需要大量工作。跳过那个。只需对匹配的所有可能起点以及您可能仍在匹配的所有可能偏移量进行双循环。

只有在您多次查询数据结构的情况下,构建这样的复杂对象才有意义。但在这里你不是,所以它没有回报。

【讨论】:

  • 谢谢btilly,大家都在讨论Z函数和前缀数组,想知道你有没有更好更高效的想法?谢谢。
  • @LinMa 请注意 Z 函数代码如何在 for 循环内有一个 while 循环?这正是我所指的双循环。
  • 感谢btilly,我阅读了参考资料并进行了更多分析,特别是有一点感到困惑。我认为可能还有另一个 l2,其中值 z[i-l2] 大于 z[i-l],它甚至可以跳过更多要比较的元素,而子字符串 [l2...r2] 不一定是作为子字符串 [l...r] 最匹配的段。任何 cmets 表示赞赏。
猜你喜欢
  • 1970-01-01
  • 2011-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-05
相关资源
最近更新 更多