最长回文子串的三种解法
0 问题描述
给定一个字符串,求其中包括的最长回文子串,返回最长回文串
0.1 问题分析
(1)回文字符串:即一个字符串与其逆置后的字符串相同,就称其为回文字符串。
(2)算法思路:找最长回文子串=枚举子串+判断回文+取长度最大
-
part 1: 枚举子串
使用两层for循环遍历字符串s,如下图所示:
代码:
# 枚举首元素 for start in range(len(s)): #枚举尾元素 for end in range(strat+1:len(s)+1): #1.判断 s[start:end] 是否是回文字符串 #2.若是且该串长度大于max_len,则更新begin 和 max_len
-
part 2: 判断回文
-
方法(1)
- 思路: 回文字符串倒置前后不变
例子:
- 代码:
if s[start:end+1]==s[start:end+1:-1]: return True else return False
- 代价:
空间上,增加了一个字符串数组的空间 ,O(n)
-
方法(2)
-
思路:如果一个字符串的第i个字符都与其倒数第i个字符相等,那么他就是回文串
-
故设立两个指针stat 和 end 起始指向首尾,并同步向中间移动
-
代码:
while start<end: if s[start]==s[end]: start += 1 end -= 1 continue else: return Flase return True
-
代价:
在方法1的基础上节约了空间的使用
-
-
-
part 3 取长度最大的字符串
-
设定begin和max_len两个变量
begin = 0 #记录最长字符串的首字符下标 max_len = 1 #记录最长字符串的长度
-
(3)算法优化:(2)中的三部分是解决问题的三个关键点。在具体的实现上,各个部分的实现方式都会影响最后程序的时间和空间复杂度,即算法的性能。所以如果要优化算法可以从这三部分的实现方式上下手。
1.暴力枚举法
1.1 思路:
找出该字符串中的所有子串,然后依次判断该子串是否为回文
1.2 代码:
#判断是否回文串
def huiwen(s:str)->int:
if len(s) == 1:
return 1
if len(s)%2==0:
i = 0
j = -1
while(i+1-j <= len(s)):
if(s[i]!=s[j]):
return 0
i+=1
j-=1
return 1
else:
i = 0
j = -1
while(i+1-j<=len(s)-1):
if(s[i]!=s[j]):
return 0
i+=1
j-=1
return 1
class Solution:
def longestPalindrome(self, s: str) -> str:
# 1. 暴力法
# 1.1 找出所有字串并存入A1中
A1 = []
for i in range(len(s)):
t = s[i]
A1.append(t)
for j in range(i+1,len(s)):
t += s[j]
A1.append(t)
# 1.2 从所有子串中找出回文串
max_len = 0
max_s = ""
for a in A1:
if huiwen(a)==1:
if len(a)>max_len:
max_len = len(a)
max_s = a
return max_s
1.3 代价:
时间复杂度 | 空间复杂度 |
---|---|
O(n^3) | O(1) |
2.中心扩展法
2.1 算法思路
遍历整个字符串,把每个字符当做回文的中心。即在渐进子串的同时实现了回文串的判断。
流程:
-
1.首先判断字符串的长度n是否小于2,如果是,则直接返回该字符串,因为长度为1的字符串本身就是回文串,长度为0的字符串也可以看作是回文串。
-
2.初始化一个空字符串ans,用来存储最长回文子串。
-
3.使用for循环枚举字符串中的每个字符i,将其作为回文串的中心位置。
-
4.分别考虑回文子串长度为奇数和偶数的情况,对于每种情况,向两端扩展,直到不能扩展为止,然后比较得到最长回文串。
-
5.如果找到了更长的回文子串,则更新ans。
-
6.遍历完所有中心位置后,返回ans。
在具体实现时字符串的长度的奇偶会导致回文中心不同,两种情如下所示:
(1)奇数时
(2)偶数时
2.2 代码
class Solution:
def expandAroundCenter(self, s, left, right):
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
return left + 1, right - 1
def longestPalindrome(self, s: str) -> str:
start, end = 0, 0
for i in range(len(s)):
left1, right1 = self.expandAroundCenter(s, i, i)#检测奇数回文串
left2, right2 = self.expandAroundCenter(s, i, i + 1)#检测偶数回文串
if right1 - left1 > end - start:
start, end = left1, right1
if right2 - left2 > end - start:
start, end = left2, right2
return s[start: end + 1]
2.3 代价:
时间复杂度 | 空间复杂度 |
---|---|
O(n^2) | O(1) |
3.动态规划法
3.1问题分析
对于一个子串而言,如果它是回文串,并且长度大于 2,那么将它首尾的两个字母去除之后,它仍然是个回文串。例如对于字符串 “ababa”,如果我们已经知道 “bab” 是回文串,那么“ababa” 一定是回文串,这是因为它的首尾两个字母都是 “a”。
动态规划有**三要素**:
- (1)状态: dp [i] [j] 表示字符串 s[i…j] 是否是回文的。
- (2)状态转移方程: dp [i] [j] = dp [i+1] [j-1] and s [i]==s[j]
- (3)边界条件:j - i < 3 , 即子串的长度为 1 或 2
3.2 算法思路
(1)初始化二维数组dp
- 数组dp: 创建一个二维数组dp。dp [i] [j] 表示字符串 s[i…j] 是否是回文的。
- 初始化 dp[i] [i] = TRUE,其余为flase
b | a | b | a | b | ||
---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | ||
b | 0 | T | ||||
a | 1 | T | ||||
b | 2 | T | ||||
a | 3 | T | ||||
b | 4 | T |
- (2) 根据状态转移方程: dp [i] [j] = (dp [i+1] [j-1] or (j - i < 3 ) ) and (s[i]==s[j] ) 填数组dp:
-
由于状态转移取决于其左下角元素,
-
故先升序填列,再升序填行
-
每填一个true,判断 j-i+1是否大于max_len:
若大于则更新begin = i,max_len = j-i+1
-
过程:
“b a”: dp [0] [1] = (dp [1] [0] | (1-0<3)) & (s[0]==s[1])
= (0|1)&(0) = 1&0 = 0
dp [1] [0] = F
begin | max_len | b | a | b | a | b |
---|---|---|---|---|---|---|
0 | 1 | 0 | 1 | 2 | 3 | 4 |
b | 0 | T | F | |||
a | 1 | T | ||||
b | 2 | T | ||||
a | 3 | T | ||||
b | 4 | T |
…
- 结果:
begin | max_len | b | a | b | a | b |
---|---|---|---|---|---|---|
0 | 5 | 0 | 1 | 2 | 3 | 4 |
b | 0 | T | F | T | F | T |
a | 1 | T | F | T | F | |
b | 2 | T | F | T | ||
a | 3 | T | F | |||
b | 4 | T |
- 返回 s[begin:begin+max_len] = “b a b a b”
3.3 代码实现
class Solution:
def longestPalindrome(self, s: str) -> str:
n = len(s)# 字符串长度
if n < 2:
return s
max_len = 1
begin = 0 #左指针
# dp[i][j] 表示 s[i..j] 是否是回文串
dp = [[False] * n for _ in range(n)]
for i in range(n):
dp[i][i] = True
# 递推开始
# 先枚举子串长度
for L in range(2, n + 1):
# 枚举左边界,左边界的上限设置可以宽松一些
for i in range(n):
# 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
j = L + i - 1
# 如果右边界越界,就可以退出当前循环
if j >= n:
break
if s[i] != s[j]:
dp[i][j] = False
else:
if j - i < 3:
dp[i][j] = True
else:
dp[i][j] = dp[i + 1][j - 1]
# 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
if dp[i][j] and j - i + 1 > max_len:
max_len = j - i + 1
begin = i
return s[begin:begin + max_len]
dp[i][j] = True
else:
dp[i][j] = dp[i + 1][j - 1]
# 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
if dp[i][j] and j - i + 1 > max_len:
max_len = j - i + 1
begin = i
return s[begin:begin + max_len]