【问题标题】:Optimal brute force solution for finding longest palindromic substring寻找最长回文子串的最佳蛮力解决方案
【发布时间】:2021-04-26 06:37:08
【问题描述】:

这是我目前的做法:

def isPalindrome(s):
    if (s[::-1] == s):
        return True
    return False

def solve(s):
    l = len(s)
    ans = ""
    
    for i in range(l):
        subStr = s[i]
        for j in range(i + 1, l):
            subStr += s[j]
            if (j - i + 1 <= len(ans)):
                continue
            if (isPalindrome(subStr)):
                ans = max(ans, subStr, key=len)

    return ans if len(ans) > 1 else s[0]

print(solve(input()))

我的代码超出了自动评分系统的时间限制。我已经花了一些时间在谷歌上查找,我发现的所有解决方案都有相同的想法,没有优化或使用动态编程,但遗憾的是我必须而且只能使用蛮力来解决这个问题。我试图通过跳过所有比最后找到的最长回文字符串短的子字符串来更早地打破循环,但最终仍无法满足时间要求。有没有其他方法可以比上述方法更早或更省时地打破这些循环?

【问题讨论】:

  • “很遗憾,我必须而且只能使用蛮力来解决这个问题” - 为什么会这样?
  • 课堂作业要求学生只使用蛮力。
  • 课堂作业需要蛮力?这对我来说有意义的唯一方法是,如果他们试图证明蛮力并不总是足够快,并且即将出现的一些问题要求您找到一种总是及时完成的​​非蛮力方法。跨度>
  • @blhsing 的答案在哪里?它唯一的缺点是它甚至不能处理回文......
  • @DuongPham GFG 的蛮力方法成本 O(n^3) 因为它重复了内部子字符串的比较,而我的解决方案没有。使用两个嵌套循环,我的解决方案成本 O(n^2) 代替。顺便说一句,我确实为偶数长度回文修复了我的解决方案。

标签: python algorithm palindrome brute-force


【解决方案1】:

使用subStr += s[j],会在之前的subStr 的长度上创建一个新字符串。使用s[::-1],来自前一个偏移量j 的子字符串被一遍又一遍地复制。两者都是低效的,因为字符串在 Python 中是不可变的,并且必须将其复制为任何字符串操作的新字符串。最重要的是,s[::-1] == s 中的字符串比较也是低效的,因为您已经在之前的迭代中比较了所有内部子字符串,并且只需要比较当前偏移量的最外面的两个字符。

您可以只跟踪迄今为止最长回文的索引和偏移量,并且只在返回时对字符串进行切片。要考虑奇数和偶数长度的回文数,您可以一次将索引增加 0.5,或者将长度加倍以避免处理浮点到整数的转换:

def solve(s):
    length = len(s) * 2
    index_longest = offset_longest = 0
    for index in range(length):
        offset = 0
        for offset in range(1 + index % 2, min(index, length - index), 2):
            if s[(index - offset) // 2] != s[(index + offset) // 2]:
                offset -= 2
                break
        if offset > offset_longest:
            index_longest = index
            offset_longest = offset
    return s[(index_longest - offset_longest) // 2: (index_longest + offset_longest) // 2 + 1]

【讨论】:

  • 您的代码不适用于简单的案例“abcddcb”。您的代码返回“a”而不是“bcddcb”
  • 好收获。我已经用修复更新了我的答案。谢谢。
【解决方案2】:

通过使用“围绕中心展开”的方法解决,感谢@Maruthi Adithya

【讨论】:

    【解决方案3】:

    您的代码的这种修改应该会提高性能。当最大可能的子字符串小于您已经计算的答案时,您可以停止您的代码。此外,您应该使用j+ans+1 而不是j+1 开始您的第二个循环,以避免无用的迭代:

    def solve(s):
        l = len(s)
        ans = ""
        
        for i in range(l):
            if (l-i+1 <= len(ans)):
                break
            subStr = s[i:len(ans)]
            for j in range(i + len(ans) + 1, l+1):
                if (isPalindrome(subStr)):
                    ans = subStr
                subStr += s[j]
        return ans if len(ans) > 1 else s[0]
    

    【讨论】:

    • 我可以看到这可以避免很多迭代,但评分系统仍然说不行。
    • 什么意思?我已经提交给评分系统,但仍然不符合时间要求。
    【解决方案4】:

    这是一个时间复杂度大于所提供的解决方案的解决方案。

    注意:这篇文章是为了更好地思考问题,并没有具体回答问题。我采用数学方法找到大于 2^L 的时间复杂度(其中 L 是输入字符串的大小)

    注意:这是一篇讨论潜在算法的帖子。你不会在这里找到答案。而且这里显示的逻辑还没有得到广泛的证明。 如果有什么我没有考虑过,请告诉我。

    方法:创建一组可能的子字符串。比较并找出这个集合中具有最高可能的回文数的最大对*。

    输入字符串示例:“abc”。

    在本例中,子字符串集有:“a”、“b”、“c”、“ab”、“ac”、“bc”、“abc”。

    • 7 个元素。

    将每个元素与所有其他元素进行比较将涉及:7^2 = 49 次计算。

    因此,输入大小为 3,计算次数为 49。

    时间复杂度

    首先计算生成子串集的时间复杂度:

    &lt;a href="https://www.codecogs.com/eqnedit.php?latex=\sum_{a=1}^{L}\left&amp;space;(&amp;space;C_{a}^{L}&amp;space;\right&amp;space;)" target="_blank"&gt;&lt;img src="https://latex.codecogs.com/gif.latex?\sum_{a=1}^{L}\left&amp;space;(&amp;space;C_{a}^{L}&amp;space;\right&amp;space;)" title="\sum_{a=1}^{L}\left ( C_{a}^{L} \right )" /&gt;&lt;/a&gt;

    (数学方程在代码sn-p中显示)

    在这里,我们从输入大小 L 中添加所有不同的子字符串大小组合。

    为了清楚起见:在上面的示例中,输入大小为 3。所以我们找到所有大小 =1 的对(即:“a”、“b”、“c”)。然后 size =2(即:“ab”,“ac”,“bc”),最后 size = 3(即:“abc”)。

    1. 因此从输入字符串中选择 1 个字符 = 一次取 L 个事物 1 个而不重复的组合。 在我们的例子中,组合数 = 3。 这在数学上可以表示为(其中 a = 1):

    &lt;a href="https://www.codecogs.com/eqnedit.php?latex=C_{a}^{L}" target="_blank"&gt;&lt;img src="https://latex.codecogs.com/gif.latex?C_{a}^{L}" title="C_{a}^{L}" /&gt;&lt;/a&gt;
    1. 同样从输入字符串中选择 2 个字符 = 3
    2. 从输入字符串中选择 3 个字符 = 1

    从具有最大长度的生成集中找到回文对的时间复杂度: 生成集的大小:N 为此,我们必须将集合中的每个字符串与集合中的所有其他字符串进行比较。

    所以 N*N 或 2 个 for 循环。因此最终的时间复杂度为:

    &lt;a href="https://www.codecogs.com/eqnedit.php?latex=\sum_{a=1}^{L}\left&amp;space;(&amp;space;C_{a}^{L}&amp;space;\right&amp;space;)^{2}" target="_blank"&gt;&lt;img src="https://latex.codecogs.com/gif.latex?\sum_{a=1}^{L}\left&amp;space;(&amp;space;C_{a}^{L}&amp;space;\right&amp;space;)^{2}" title="\sum_{a=1}^{L}\left ( C_{a}^{L} \right )^{2}" /&gt;&lt;/a&gt;

    对于 L > 1,这是大于 2^L 的发散函数。

    但是,可以对此进行多种优化。例如:没有必要将“a”与“abc”进行比较,因为“a”也会与“a”进行比较。即使应用了这种优化,它的时间复杂度仍然 > 2^L(对于大多数情况)。

    希望这能给你一个新的视角来解决这个问题。

    PS:这是我的第一篇文章。

    【讨论】:

      【解决方案5】:

      您不应该从该字符串的开头找到该字符串,但您应该从它的中间开始并展开当前字符串

      例如,对于字符串 xyzabccbalmn,您的解决方案将花费 ~ 6 * 11 次比较,但从中间搜索将花费 ~ 11 * 2 + 2 次操作

      但无论如何,暴力破解永远无法确保您的解决方案对任何任意字符串都运行得足够快。

      【讨论】:

      • 偶数呢?
      • 同理,小事一桩,没什么区别
      【解决方案6】:

      试试这个:

      def solve(s):
          if len(s)==1:
              print(0)
              return '1'
          if len(s)<=2 and not(isPalindrome(s)):
              print (0)
              return '1'
          elif isPalindrome(s):
              print( len(s))
              return '1'
          elif isPalindrome(s[0:len(s)-1]) or  isPalindrome(s[1:len(s)]):
                print (len(s)-1)
                return '1'
          elif  len(s)>=2:      
               solve(s[0:len(s)-1])
               return '1'
          return 0
      

      【讨论】:

        猜你喜欢
        • 2023-03-19
        • 2019-06-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-11-11
        • 2012-04-23
        • 1970-01-01
        相关资源
        最近更新 更多