【问题标题】:Trying to calculate algorithm time complexity试图计算算法时间复杂度
【发布时间】:2021-02-25 02:59:27
【问题描述】:

所以昨晚我解决了this LeetCode question。我的解决方案不是很好,很慢。所以我试图计算我的算法的复杂度,以与 LeetCode 在解决方案部分中列出的标准算法进行比较。这是我的解决方案:

class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        # Get lengths of all strings in the list and get the minimum
        # since common prefix can't be longer than the shortest string.
        # Catch ValueError if list is empty
        try:
            min_len = min(len(i) for i in strs)
        except ValueError:
            return ''

        # split strings into sets character-wise
        foo = [set(list(zip(*strs))[k]) for k in range(min_len)]

        # Now go through the resulting list and check whether resulting sets have length of 1
        # If true then add those characters to the prefix list. Break as soon as encounter
        # a set of length > 1.
        prefix = []
        for i in foo:
            if len(i) == 1:
                x, = i
                prefix.append(x)
            else:
                break
        common_prefix = ''.join(prefix)
        return common_prefix

我在计算复杂性方面有点吃力。第一步 - 获取字符串的最小长度 - 需要 O(n) ,其中 n 是列表中的字符串数。然后最后一步也很简单——它应该花费 O(m),其中 m 是最短字符串的长度。

但中间的部分令人困惑。 set(list(zip(*strs))) 应该希望再次采用 O(m),然后我们这样做 n 次,所以 O(mn)。但是总体复杂度为 O(mn + m + n),这对于解决方案的速度来说似乎太低了。

另一种选择是中间步骤是O(m^2*n),这更有意义。这里计算复杂度的正确方法是什么?

【问题讨论】:

  • 旁注:我发现 leetcode 报告的运行时可能受到外部因素的影响。因此,如果您的结果看起来很糟糕,请不要紧张,因为它可能只是一个缓慢的服务器。
  • 另一件事是扫描许多字符串的第 ??th 个字符会导致缓存局部性较差;即使复杂度可能相同,这也会使程序运行速度变慢。

标签: python algorithm time-complexity


【解决方案1】:

是的,中间部分是O{mn},整体是O{mn},因为对于mn 的大值,这使O{m}O{n} 相形见绌。

您的解决方案具有理想的运行时复杂性顺序。

优化:短路

但是,您可能对其他人有更快的解决方案感到沮丧。我怀疑其他人可能会在第一个不匹配的索引上短路。

让我们考虑一个包含 26 个字符串 (['a'*500, 'b'*500, 'c'*500, ...]) 的测试用例。您的解决方案将继续创建一个 500 长的列表,每个条目包含一组 26 个元素。同时,如果短路,则只处理第一个索引,即一组 26 个字符。

尝试将您的list 更改为generator。这可能就是您需要短路的全部内容。

foo = (set(x) for x in zip(*strs)))

您可以跳过min_len 检查,因为zip 的默认行为是只迭代最短输入。

优化:生成中间结果

我看到您将每个字母附加到一个列表中,然后是''.join(lst)。这是有效的,尤其是与迭代附加到字符串的替代方案相比。

但是,我们可以很容易地保存一个计数器match_len。然后当我们检测到第一个不匹配时,只需:

return strs[0][:match_len]

【讨论】:

  • 从列表推导式创建一个集合仍然会评估整个集合。
  • 啊,我明白你的意思了; foo = (set(list(zip(*strs))[k]) for k in range(min_len)) 确实会逐步构建它。
  • 代码foo = (set(zip(*strs)))等价于foo = set(zip(*strs))并立即构造完整集。
  • 谢谢@sabik。我没有意识到您需要在最外面的括号内添加一个 for 才能将最外面的括号实际转换为生成器。已相应更新:foo = (set(x) for x in zip(*strs)))
  • @RazzleShazl 太棒了!有了这个微小的变化,解决方案突然比所有其他解决方案的 94.1% 都快,运行时间从大约 60 毫秒下降到 28 毫秒!
猜你喜欢
  • 2011-06-21
  • 2021-08-25
  • 1970-01-01
  • 2021-03-03
  • 1970-01-01
  • 1970-01-01
  • 2015-03-09
相关资源
最近更新 更多