题目来源:https://leetcode-cn.com/problems/longest-palindromic-substring/
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:输入: "cbbd"
输出: "bb"
本题有很多种解法,最简单的暴力求解,但是会超时。下面分别说明动态规划法和中心扩散法。
解法一:动态规划法
为什么可以用动态规划呢?
- 对于一个回文字符串,其子串也必定是回文字符串;
- 对于一个字符串,若其子串不是回文,则它也必定不是回文;
- 对于一个字符串,只有其头尾两个字符相等,才有必要继续判断是否为回文。
那么,一个字符串是否是回文就取决于其子串是否是回文。
- 定义状态:dp[i][j]表示字符串s[i~j]是否为回文。
- 思考状态转移方程:根据前面的分析可知,一个字符串是否是回文,一看头尾两字符是否相等,若相等则取决于子串是否是回文。于是有:dp[i][j] = (s[i] == s[j]) and dp[i+1][j-1]
- 初始化:初始化状态矩阵为False。由于任意单个字符都是回文,于是有dp[i][i] = True。
- 考虑输出:只要得到dp[i][j] = True,就记录起始位置i和字符串长度cur_length,更新记录。(不需要每次都直接取出回文字符串,因为会增加耗时)
- 状态压缩:减少不必要的额外开销。(下面的代码暂时不做这一步)
根据以上思路书写代码:
class Solution(object):
def longestPalindrome(self, s):
"""
:type s: str
:rtype: str
"""
size = len(s)
if size < 2: # 考虑特殊状态,无字符或单字符直接返回其本身
return s
dp = [[False] * size for i in range(size)] # 初始化状态矩阵
max_length = 1 # 初始化最大长度
start = 0 # 初始化最大回文起点
for i in range(size): # 初始化对角线(单字符为回文)
dp[i][i] = True
for j in range(1, size): # 终点从s[1]开始
for i in range(0, j): # 起点从s[0]开始,到s[j]结束
if s[i] == s[j]: # 头尾字符相等
if j - i < 3: # 长度小于3,2~0字符此时均可判断为回文
dp[i][j] = True
else: # 取决于子字符串的状态(是否为回文)
dp[i][j] = dp[i+1][j-1]
else:
dp[i][j] = False # 头尾不相等,一定不是回文
if dp[i][j]: # 每确定一个回文,更新一次最大长度和回文起点
cur_length = j - i + 1
if cur_length > max_length:
max_length = cur_length
start = i
return s[start:start + max_length]
时间复杂度: (两层循环)
空间复杂度: (状态矩阵dp的大小为n*n)
解法二:中心扩散法
动态规划法相当于从大字符串回溯到小字符串来完成判断,那么能不能从中间某个字符开始向外扩散实现判断呢?
中心扩散法就是这样的思想,以字符串中任意一个字符为初始点,向左右逐单位扩散,若扩散后还是回文,则继续扩散,否则记录起点和回文长度。
根据字符串长度的不同,分为奇数中心和偶数中心:
- 字符串长度为奇数:中心是单字符,如'aba'的中心是'b';
- 字符串长度为偶数:中心是两个字符的间隙,如'abba'的中心是'bb'间的间隙。
设计一种算法,兼容这两种情况。
class Solution(object):
def longestPalindrome(self, s):
"""
:type s: str
:rtype: str
"""
n = len(s)
if n < 2: # 特殊情况,单字符或空字符
return s
max_length = 1
ans = s[0]
for i in range(n):
palindrome_odd, max_odd = self.find_palindrome(s, n, i, i)
palindrome_eve, max_eve = self.find_palindrome(s, n, i, i + 1)
palindrome = palindrome_eve if max_eve >= max_odd else palindrome_odd
if len(palindrome) >= max_length: # 每次都更新最大回文及其长度
max_length = len(palindrome)
ans = palindrome
return ans
def find_palindrome(self, s, n, i, j): # 从中心开始扩散,寻找最大回文及其长度
while i >= 0 and j < n and s[i] == s[j]:
i -= 1
j += 1
return s[i+1:j], (j - i - 1) # 最后一次循环后i和j多改变了一次
时间复杂度: (枚举扩散中心为,寻找最大回文也为)
空间复杂度: (变量数为常数,不随N改变(递归时才会每次分配不同存储空间))
解法三:马拉车算法
暂时放置。。
参考: