问题来源:最长回文子串
问题描述:给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
例子:
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
1. O ( n 3 ) O(n^3) O(n3)算法
最简单也是最直观的做法是暴力算法。通过两层遍历,我们可以选出所有可能的子串,然后判断该子串是否是回文。
class Solution:
def longestPalindrome(self, s: str) -> str:
max_huiwen = ''
for i in range(len(s)):
for j in range(i+1, len(s)+1):
# 判断s[i:j]是否是回文
ss = s[i:j]
if ss == ss[::-1]:
if len(max_huiwen) < len(ss):
max_huiwen = ss
return max_huiwen
这种算法注定用时最长,可能超时。
2. O ( n 2 ) O(n^2) O(n2)算法
暴力算法实际上重复判断了很多子串,比如,我们判断“abcba”是否为回文,我们不但判断子串“abcba”是否为回文,也判断子串“bcb”是否为回文。实际上,我们在判断子串“bcb”是回文之后,应该保留这种信息,然后判断子串“abcba”是否为回文。
我们需要判断每个较小的子串是否为回文,基于此,我们进一步判断较长子串是否为回文。
动态规划适用条件是,该问题较大规模问题可以由较小问题解决,以及该问题有最优子结构,也就是说,该问题的最优解中包含子问题的最优解。可以看到,判断子串是否为回文,可以分解为子串的子串是否为回文。
动态规划有三点:状态定义,状态转移方程,边界值。
- 状态定义: d ( i , j ) d(i, j) d(i,j)存储bool值,用于判断子串s[i, j+1]是否为回文;
- 状态转移方程: d ( i , j ) = ( s [ i ] = = s [ j ] ) and d ( i + 1 , j − 1 ) d(i, j) = (s[i]==s[j])~ \text{and}~ d(i+1, j-1) d(i,j)=(s[i]==s[j]) and d(i+1,j−1),意思是,字符串s[i, j+1]是否为回文,取决于1)字符串左右两端字符是否相等,即s[i]==s[j];且2)子串s[i+1, j-1]是否为回文;
- 边界值:如果j==i,则 d ( i , j ) = True d(i, j)=\text{True} d(i,j)=True;如果j==i+1,那么只需判断字符串两端是否相同;如果j>=i+2,那么用状态转移方程判断
具体代码如下:
class Solution:
def longestPalindrome(self, s: str) -> str:
d = [[False for i in range(len(s))] for j in range(len(s))]
# 子串长度i从1(自身)到len(s)
for i in range(len(s)):
# 给定长度i,子串从j开始
for j in range(len(s)-i):
if i == 0: # 子串长度为1
d[j][j+i] = True
elif i == 1: # 子串长度为2
d[j][j+i] = (s[j] == s[j+i])
else: # 子串长度为3及以上
# 长度为i的子串变成长度为i-2的子串
d[j][j+i] = (s[j] == s[j+i]) and d[j+1][j+i-1]
#print(d)
# 在二维数组d中,寻找值为True且col-row最大的项
max_item = -1
start, end = -1, len(s)
for i in range(len(s)):
for j in range(len(s)):
if d[i][j] and j - i > max_item:
max_item = j - i
start, end = i, j
return s[start:end+1]