欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

最长回文子串

程序员文章站 2022-07-13 21:58:56
...


给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:

输入: "cbbd"
输出: "bb"

暴力法

将选出所有子字符串可能的开始和结束位置,并检验它是不是回文。
时间复杂度:O(n^2),往往利用python的切片可以很好的缩减复杂度
如果不用切片,还需要遍历一次子字符串,时间复杂度就是O(^3)
空间复杂度:O(1)

ps: 官方给出的时间复杂度是没有考虑python切片的功能

python
def force(self, s: str) -> str: 
        if s==s[::-1]:
            return s
        max_len = 1
        res = s[0]
        for i in range(len(s) - 1):
            for j in range(i + 1, len(s)):
                if j - i + 1 > max_len and s[i:j+1] == s[i:j+1][::-1]:
                    max_len = j - i + 1
                    res = s[i:j + 1]
        return res

动态规划 - 中心扩散法

时间复杂度 O(N^2)
空间复杂度⾄少要 O(N^2) 来存储 DP table。(推荐双指针,只需O(1)空间)

:
为了改进暴力法,我们首先观察如何避免在验证回文时进行不必要的重复计算。考虑“ababa” 一定是回文,因为它的左首字母和右尾字母是相同的。
我们给出 P(i,j) 的定义如下:
如果子串S_i和S_j是回文字符串则P(i,j)为ture
其他情况,P(i,j)为false
因此 P(i,j)=(P(i+1,j−1) and S_i==S_j)

基本示例如下:
P(i, i) = true
P(i, i+1) = ( S_i == S_{i+1} )
这产生了一个直观的动态规划解法,我们首先初始化一字母和二字母的回文,然后找到所有三字母回文,并依此类推…

 #中心扩散法Spread From Center
    def spread(self, s, left, right):
        """
        left = right 的时候,此时回文中心是一条线,回文串的长度是奇数
        right = left + 1 的时候,此时回文中心是任意一个字符,回文串的长度是偶数
        """

        while left >= 0 and right < len(s) and s[left] == s[right]:
            left -= 1
            right += 1
        return s[left + 1:right]
   

# 动态规划法-中心扩散法Spread From Center
    def spread_from_center(self, s:str) -> str:
        
        if s==s[::-1]:
            return s
        res = s[:1]
        for i in range(len(s)):
            palindrome_odd= self.spread(s,i, i)
            palindrome_even= self.spread(s,i, i + 1)
            # 当前找到的最长回文子串
            res = max(palindrome_odd,palindrome_even,res,key=len)
        return res


双指针 - 中心扩散

回⽂串就是正着读和反着读都⼀样的字符串
回⽂串的⻓度可能是奇数也可能是偶数

寻找回⽂串的问题核⼼思想是:从中间开始向两边扩散来判断回⽂串,主串从头到尾:找到以 s[i] 为中⼼的回⽂串,假设s[i]为子串中心,向两边扩展进行判断

for 0 <= i < len(s):
	找到以 s[i] 为中⼼的回⽂串
	更新答案

但是呢,回⽂串的⻓度可能是奇数也可能是偶数,如果是abba 这种情况,没有⼀个中⼼字符,上⾯的算法就没辙了。所以我们可以修改⼀下:

for 0 <= i < len(s):
	找到以 s[i] 为中⼼的回⽂串
	找到以 s[i] 和 s[i+1] 为中⼼的回⽂串
	更新答案

实现⼀个函数来寻找最⻓回⽂串

p(s, i, i):找到以 s[i] 为中⼼的回⽂串
c a b  c  b a a
 --  (l/r)
----l --- r     (l--,r++)while(l >= 0 && r < s.size()&& s[l] == s[r]
--l-------- r
l-------------r 此时跳出while,l+1是回文子串的开始,r-1是回文子串的结尾,回文子串长度是此时两指针间长度-2(r-l+1)-2
 所以回文子串是s.substr(l+1, r-l-1)
p(s, i, i+1):找到以 s[i],s[i+1] 为中⼼的回⽂串
c a b b a d
 -- l r 
--l --- r     (l--,r++)while(l >= 0 && r < s.size()&& s[l] == s[r]
l---------r 此时跳出while,l+1是回文子串的开始,r-1是回文子串的结尾,回文子串长度是此时两指针间长度-2(r-l+1)-2
 所以回文子串是s.substr(l+1, r-l-1)
string palindrome(string& s, int l, int r) {
	// 防⽌索引越界
	while (l >= 0 && r < s.size()&& s[l] == s[r]) {
		// 向两边展开
		l--; r++;
	}
	// 返回以 s[l] 和 s[r] 为中⼼的最⻓回⽂串
	return s.substr(l + 1, r - l - 1);
}

传⼊两个指针 l 和 r 实现可以同时处理回⽂串⻓度为奇数和偶数的情况:

for 0 <= i < len(s):
	# 找到以 s[i] 为中⼼的回⽂串
	palindrome(s, i, i)
	# 找到以 s[i] 和 s[i+1] 为中⼼的回⽂串
	palindrome(s, i, i + 1)
	更新答案

longestPalindrome 的完整代码:
时间复杂度 O(N^2),空间复杂度 O(1)


string longestPalindrome(string s) {
	string res;
	for (int i = 0; i < s.size(); i++) {
		// 以 s[i] 为中⼼的最⻓回⽂⼦串
		string s1 = palindrome(s, i, i);
		// 以 s[i] 和 s[i+1] 为中⼼的最⻓回⽂⼦串
		string s2 = palindrome(s, i, i + 1);
		// res = longest(res, s1, s2)
		res = res.size() > s1.size() ? res : s1;
		res = res.size() > s2.size() ? res : s2;
	}
	return res;
}

python解答

class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        def palindrome(s, l, r):
            while l>=0 and r<len(s) and s[l] == s[r]:
                l=l-1
                r=r+1
            return s[l+1: r]# 切片,需要首尾即可
        res=''
        for i in range(len(s)):
            s1 = palindrome(s, i, i)
            s2 = palindrome(s, i, i+1)
            res = res if len(res) > len(s1) else s1
            res = res if len(res) > len(s2) else s2
        return res

  1. 题目中“你可以假设 s 的最大长度为 1000”:说明时间复杂度不能超过O(n^2),1千的平方正好是1百万
    1百万规则:输入规模代入时间复杂度公式,超过1百万很可能超时
  2. 判断一个字符串是否回文
 a b c b a
 0 1 - -  - 只判断 0到n/2处的字符即可:for i in range(n/2)  
 i-------n-i-1if s[i] != s[n-i-1]: return false

最长回文子串

# entire string
def isPalindrome(s):
	n = len(s)
	for i in range(n/2):
		if s[i] != s[n-i-1]: return false
	return true

  1. 判断一个str的子串是否回文
a b c b a  (b b)
l  - -- r  从子串两端开始
  l - r
# substring s[l~r]
def isPalindrome(s,l,r):
	while l<r:
		if s[l] != s[r]: return false
		l++
		r--
	return true
		

Python逻辑运算符 : and or not
结尾不用分号;
在python中是没有自增和自减的,但在python中存在 i = i + 1和 i = i -1 的情况。
Python代码块使用缩进对齐表示代码逻辑,而不是使用大括号
没有三元运算符: res = res if len(res) > len(s1) else s1
Python中字符串切片:字符串[开始索引:结束索引:步长],切取字符串为开始索引到结束索引-1内的字符串,切片需要首尾即可
num_str_1 = num_str[2:6] # 截取2 - 5位置的字符
python中创建变量不需要声明,变量赋值不需要类型声明。

相关标签: leetcode