zoukankan      html  css  js  c++  java
  • 【Leetcode刷题】最长回文子串

    主要记录解题过程,反思如何构思代码。

    原题:https://leetcode-cn.com/problems/longest-palindromic-substring

    题目:

    给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
    
    示例 1:
    
    输入: "babad"
    输出: "bab"
    注意: "aba" 也是一个有效答案。
    示例 2:
    
    输入: "cbbd"
    输出: "bb"
    

    解题过程

    看到这题一开始是完全懵逼的,看着两个例子想了一个错的解法:用两个指针指向字符串的首尾,当两个指针所指的内容相同时,记录下两个索引值,不同时索引值归零,直到两个指针相遇。

    这个解法很明显是错的,因为它假定了最长回文子串的中心一定是字符串中心

    这个时候我看到了题目的三条提示:

    How can we reuse a previously computed palindrome to compute a larger palindrome?
    
    If “aba” is a palindrome, is “xabax” and palindrome? Similarly is “xabay” a palindrome?
    
    Complexity based hint:
    If we use brute-force and check whether for every start and end position a substring is a palindrome we have O(n^2) start - end pairs and O(n) palindromic checks. Can we reduce the time for palindromic checks to O(1) by reusing some previous computation.
    

    前两条提示非常有用,我想到回文的特征是:呈中心对称。即,两个指针以同样的步幅从回文的中心出发,所指内容应该会一直保持一致。应该还是使用两个指针,但是是从内到外走,而不是从外到内

    总体算法思路为:遍历整个字符串,将每个位置当作回文中心去找以这个字符为中心能形成的最长回文,然后选出整个字符串最长的回文

    这里有一个很干扰我思路的问题出现了:就是'cbbd'这种情况。我的算法是无法输出'bb'的,在这个地方卡了很久。后来我决定把这种情况特殊处理,如果第i个字符串与第i+1个字符串相同,则将尾指针往后移。

    我觉得有一个经验就是,在时间紧迫的时候,先把自己能想出来的完整算法写出来,再想办法去处理特殊情况,否则在那空想是很浪费时间的。

    第一版:

    class Solution:
        def longestPalindrome(self, s: str) -> str:
            n = len(s)
            longest_palindrome = {'length': 0, 'str': ''}
            for i in range(n):
                index1 = index2 = i
                if i+1 < n and s[i+1] == s[i]:
                    index1 = i
                    index2 = i+1
                while index1-1 >= 0 and index2+1 < n and s[index1-1] == s[index2+1]:
                    index1 += -1
                    index2 += 1
                length = index2 - index1 + 1
                if length > longest_palindrome['length']:
                    longest_palindrome['length'] = length
                    longest_palindrome['str'] = s[index1:index2+1]
            return longest_palindrome['str']
    

    结果:不通过

    不通过的用例:

    输入:
    "ccc"
    输出:
    "cc"
    预期:
    "ccc"
    

    在第二个c的位置,由于第三个c与第二个c相等,则index1=1,index2=2,将回文的中心变成了这两个字符,所以出错。

    解决的思路是:找全所有所有相同的字符组成一个回文中心,即在处理第一个c的时候,就一直往后找,直到回文中心变为"ccc"

    第二版:

    class Solution:
        def longestPalindrome(self, s: str) -> str:
            n = len(s)
            longest_palindrome = {'length': 0, 'str': ''}
            for i in range(n):
                index1 = index2 = i
                while index2+1 < n and s[index1] == s[index2+1]:
                    index2 += 1
                while index1-1 >= 0 and index2+1 < n and s[index1-1] == s[index2+1]:
                    index1 += -1
                    index2 += 1
                length = index2 - index1 + 1
                if length > longest_palindrome['length']:
                    longest_palindrome['length'] = length
                    longest_palindrome['str'] = s[index1:index2+1]
            return longest_palindrome['str']
    

    可以再优化一下,不要每次找到更长的回文就切出来,只需要记下索引就好了

    第三版:

    class Solution:
        def longestPalindrome(self, s: str) -> str:
            n = len(s)
            l_len = 0
            l_index1 = 0
            l_index2 = 0
            for i in range(n):
                index1 = index2 = i
                while index2+1 < n and s[index1] == s[index2+1]:
                    index2 += 1
                while index1-1 >= 0 and index2+1 < n and s[index1-1] == s[index2+1]:
                    index1 += -1
                    index2 += 1
                length = index2 - index1 + 1
                if length > l_len:
                    l_len = length
                    l_index1 = index1
                    l_index2 = index2
            return s[l_index1: l_index2+1]
    
    

    二刷

    就因为加了“下一次mid的选择越过相同的字符”这一个操作,执行用时从800ms降到90ms

    应该是测试用例中有很多连在一起的相同字符

    class Solution:
        def longestPalindrome(self, s: str) -> str:
            # 遍历s,将每个字符当作回文中心扩散
            n = len(s)
            if n == 0:
                return ""
            # 记录最长的回文子串,初始为第一个字符
            res = s[0]
            # 回文中心
            mid = 0
            while mid < n:
                # left和right指向回文子串的前一个字符和后一个字符
                left, right = mid - 1, mid + 1
                # 找全所有相同的字符组成回文中心
                while right < n and s[right] == s[mid]:
                    right += 1
                    # 下一次mid的选择应该越过这些相同的字符
                    mid += 1
                while left >= 0 and right < n and s[left] == s[right]:
                    left -= 1
                    right += 1
                # 当前回文串的长度
                if right - left - 1 > len(res):
                    res = s[left+1:right]
                mid += 1
            return res
    

    时间复杂度:O(n2)。最多有n个回文中心,每个回文中心最多向外拓展n次

    空间复杂度:O(1)

  • 相关阅读:
    【机器学习】Softmax 和Logistic Regression回归Sigmod
    【LDA】线性判别式分析
    【MLE】最大似然估计Maximum Likelihood Estimation
    n阶方阵A可逆充分必要条件
    【机器学习】K-Means算法
    【X-Forwarded-For】WEB修改访客IP
    【Ubuntu】ubuntu系统下python3和python2环境自由切换
    【Python】打印object对象
    【linux】dpkg info修复及dpkg: warning: files list file for package
    【php】https请求
  • 原文地址:https://www.cnblogs.com/luozx207/p/12177741.html
Copyright © 2011-2022 走看看