LeetCode:5. Longest Palindromic Substring

LeetCode:5. Longest Palindromic Substring

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example 1:

Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.

Example 2:

Input: "cbbd"
Output: "bb"

题目的意思就是求一个字符串的最大连续回文子串。

思路一:滑动窗口

其实这个滑动窗口的方法也是在暴力求解法的基础上的一个改进,由于暴力求解法时间通不过所以这里就不写出来了。

思路是这样的,假设某个时刻最长回文字串长度为 maxLen,对于位置i(i=0,1,…,n),设一个窗口左侧为i,右侧为j(j=i,i+1,…,n),窗口大小为 maxLen。判断这个子串是否是回文。如果是,则更新最大长度的保存值maxLen。这样就跳过了比maxLen小的字串,每次都保证查找的子串长度大于等于 maxLen。

Python 代码实现

class Solution:
    
    def isPalindromic(self,s: str) -> bool:
        sLen = len(s)
        if sLen == 1:
            return True
        mid = int((sLen)/2)
        i = 0
        for i in range(0,mid):
            if s[i] != s[sLen-i-1]:
                return False
        return True
    
    def longestPalindrome(self, s: str) -> str:
        ans=""
        sLen = len(s)
        if sLen == 1:
            return s
        maxLen = 0
        i = 0
        j = 0
        k = 0
        for i in range(sLen):
            left = i
            right = i+ maxLen
            right = right+1 if left == right else right
            while right <= sLen:
                if self.isPalindromic(s[left:right]) and len(s[left:right]) >= maxLen:
                    ans=s[left:right]
                    maxLen = len(s[left:right])
                right+=1
        return ans

复杂度分析

最里面判断是否是回文的时间是 O ( n 2 ) O(\frac{n}{2}) O(2n)。外面双层循环是 O ( n 2 ) O(n^2) O(n2),所以其实这个滑动窗口的方法时间复杂度还是 O ( n 3 ) O(n^3) O(n3)。不过系统还是可以AC的。

思路二:动态规划

举个例子,有字符串 “ababa” ,如果我们已经知道 “bab” 是一个回文子串,很显然,只有当"bab"的左右两侧字符串相同时 , "ababa"才是回文。

我们定义函数 P(i,j)如下:

P ( i , j ) = { t r u e , S i , S i + 1 , . . . , S j   ( p a l i n d r o m e ) f a l s e , o t h e r w i s e P(i,j)= \begin{cases} true, & S_i,S_{i+1},...,S_j\:(palindrome)\\ false, & otherwise \end{cases} P(i,j)={true,false,Si,Si+1,...,Sj(palindrome)otherwise

也就是说当 S i , S i + 1 , . . . , S j S_i,S_{i+1},...,S_j Si,Si+1,...,Sj 这个字符串是回文的时候 P(i,j) 为true,否为false。因此 P ( i , j ) = ( ( P ( i + 1 , j − 1 ) = = t r u e ) a n d ( S i = = S j ) ) P(i,j)=((P(i+1,j-1)==true)and(S_i==S_j)) P(i,j)=((P(i+1,j1)==true)and(Si==Sj))。此外要注意的最基本的两个点:

P ( i , i ) = t r u e P(i,i)=true P(i,i)=true

P ( i , i + 1 ) = ( S i = = S i + 1 ) P(i,i+1)=(S_i==S_{i+1}) P(i,i+1)=(Si==Si+1)

Python 代码实现

""" 
dynamic programming
规则,如果P(i,j)是回文字符串,那么S[i]=S[j],且P(i+1, j-1)也是回文串。
一个变量保存目前位置之前的最长回文子串 maxl
我们用一个boolean dp[l][r]表示字符串从i到j这段是否为回文。
试想如果dp[l][r]=true,我们要判断dp[l-1][r+1]是否为回文,只需要判断字符串在(l-1)和(r+1)两个位置是否为相同的字符,是不是减少了很多重复计算。

进入正题,动态规划关键是找到初始状态和状态转移方程。
初始状态,l=r时,此时dp[l][r]=true。
状态转移方程,dp[l][r]=true并且(l-1)和(r+1)两个位置为相同的字符,此时dp[l-1][r+1]=true

"""
class Solution:
    
    def longestPalindrome(self, s: str) -> str:
        st = 0
        maxl = 0
        sLen = len(s)
        dp = [[0 for i in range(sLen)] for j in range(sLen)]
        for i in reversed(range(len(s))):
            for j in range(i,len(s)):
                if s[i] == s[j] and (i+1>j-1 or dp[i+1][j-1] ):
                    dp[i][j] = 1
                    if j-i+1 > maxl:
                        st = i
                        maxl = j-i+1
        return s[st:st+maxl]

这里值得注意的是,i要从后向前遍历。然后剩下的就是按照上面的公式写出代码即可。
里面的i+1>j-1 这个条件,其实就是防止左右边界溢出(i=sLen)。

时间复杂度

动态规划的方法时间复杂度显示为 O ( n 2 ) O(n^2) O(n2)

思路三:Expand Around Center

官方的解答中有个叫 Expand Around Center 的方法。主要思想就是回文字符串总是中心对称的。一个长为n的字符串有2n-1个字串,只要判断这些子串是不是中心对称即可。当然实现的方法也很巧妙,注意下面的两个len:len1和len2,前一个是以当前这一个字符向左右展开(Expand),判断左右字符串是否对称(调用expandAroundCenter方法),后一个是以相邻两个字符串向左右展开,判断左右字符串是否对称,然后比较两个长度大小,取较长的。最后根据这个长度,以及当前字符位置,得到左右边界,从而得到一个回文子串。

Python 代码实现

# Expand Around Center
class Solution:
    
    def expandAroundCenter(self,s: str, left: int, right: int)-> int:
        l = left
        r = right
        while(l >= 0 and r < len(s) and s[l] == s[r]):
            l-=1
            r+=1
        return r-l-1
    
    def longestPalindrome(self, s: str) -> str:
        if (s is None or len(s) < 1):
            return ""
        start = 0
        end = 0
        for i in range(len(s)):
            len1 = self.expandAroundCenter(s, i, i)
            len2 = self.expandAroundCenter(s, i, i + 1)
            lenMax = max(len1, len2)
            if (lenMax > end - start):
                start = i - int((lenMax - 1) / 2)
                end = i + int(lenMax / 2)
        print(start,end)
        return s[start:end+1]

复杂度

上面动态规划的方法用到了一个二维数组,所以空间复杂度为 O ( n 2 ) O(n^2) O(n2),而这个 Expand Around Center 的方法的空间复杂度只有O(1)。两者的时间复杂度都为 O ( n 2 ) O(n^2) O(n2)


THE END.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值