【问题标题】:What's the time complexity of this algorithm finding the longest palindromic substring?这个算法找到最长回文子串的时间复杂度是多少?
【发布时间】:2018-09-09 21:47:45
【问题描述】:

这是 Python 代码:

def is_palindrome(s):
    return s == s[::-1]


def longestp(s):
    if is_palindrome(s):
        return s

    maxp = s[0]

    for i in range(len(s)-1):
        half_length = len(maxp) // 2
        start = i - half_length
        end = i + half_length

        while start >= 0 and end <= len(s)-1:
            if is_palindrome(s[start:end+2]):
                if len(s[start:end+2]) > len(maxp):
                    maxp = s[start:end+2]
                end += 1
            elif is_palindrome(s[start:end+1]):
                if len(s[start:end+1]) > len(maxp):
                    maxp = s[start:end+1]
                start -= 1
                end += 1
            else:
                break

    return maxp

我最初认为它是O(n^3),因为有两个嵌套循环和字符串切片,但在我的测试中它几乎是线性的。是否有任何类型的输入会使该算法变慢?

【问题讨论】:

  • “它在我的测试中几乎是线性的”:切片增加了复杂性,但它不像 python 循环那么慢。你是如何测量“线性”边的?
  • 除此之外:我确信可以完成比len(s[start:end+1]) 更快的事情。切片字符串只是为了计算长度只是次优的。我认为你最好用开始和结束参数重写is_palindrome,并且在你的所有程序中都不要切片。这可能会更快(并且不太复杂:))
  • how did you measure the "linear" side? - 使用 100、1k、10k 字符的输入字符串(随机和回文)运行 %timeit
  • @EugeneYarmash “字符串”是什么?您如何确保 10k 字符串包含不同长度的回文子字符串?
  • 你可能用回文来计时,这符合if is_palindrome(s):的情况。

标签: python string python-3.x substring palindrome


【解决方案1】:

该算法看起来好像需要与成正比的总时间

integral_0^N x dx = [(x^2)/2]_0^N = (N^2)/2 = O(N^2)

匹配ab* 的字符串应该给出最坏的情况。

这是一段代码,它通过实验演示了最坏情况的行为。

结构如下:

  1. 定义 worstCase 函数,该函数构造长度为 N 的“坏”字符串
  2. 在这些字符串上测量函数的时间
  3. 创建log(N)log(time(N)) 的数据集
  4. 拟合一条线,尝试估计线的斜率:这是您的O(N^p) 中的指数p

代码如下:

def worstCase(length):
  return "a" + "b" * (length - 1)

from time import clock
from math import log

xs = []
ys = []
for n in [4 * int(1000 * 1.2 ** n) for n in range(1, 20)]:
  s = worstCase(n)
  assert len(s) == n
  startTime = clock()
  p = longestp(s)
  endTime = clock()
  assert p == s[1:]
  t = endTime - startTime
  xs.append(log(n))
  ys.append(log(t))
  print("%d -> %f" % (n, endTime - startTime))

from numpy import polyfit

exponent, constant = polyfit(xs, ys, 1)

print("Exponent was: %f" % (exponent))

这是输出(需要一两分钟):

4800 -> 0.057818
5760 -> 0.078123
6908 -> 0.105169
8292 -> 0.145572
9952 -> 0.197657
11940 -> 0.276103
14332 -> 0.382668
17196 -> 0.534682
20636 -> 0.747468
24764 -> 1.048267
29720 -> 1.475469
35664 -> 2.081608
42796 -> 2.939904
51356 -> 4.216063
61628 -> 5.963550
73952 -> 8.691849
88744 -> 12.126039
106492 -> 19.684188
127788 -> 24.942766
Exponent was: 1.867208    

它估计指数约为 1.86,比 3 更接近 2。

【讨论】:

    【解决方案2】:

    这绝对不是线性的。尝试使用包含大量回文但不是回文的输入:

    >>> timeit.timeit('longestp(x)', 'x="a"*100000+"b"', globals=globals(), number=1)
    5.5123205203562975
    >>> timeit.timeit('longestp(x)', 'x="a"*10000+"b"', globals=globals(), number=1)
    0.08460151217877865
    

    切片和s == s[::-1] 具有比解释的Python 代码更好的常数因子,并且您需要确保内部循环不会提前breaking。这些影响可能会打乱您通过时间来判断时间复杂度的尝试。


    我也不认为它是 O(n^3)。由于break 条件,嵌套循环不会按照您可能直观期望的方式进行交互。内部循环在整个算法过程中执行 O(n) 次迭代,因为在有限次数的迭代之后,len(maxp) 增长,或者循环 breaks。这个算法在我看来是最坏情况 O(n^2)。

    【讨论】:

      猜你喜欢
      • 2019-02-16
      • 2023-03-24
      • 2014-08-26
      • 2015-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多