5. 最长回文子串

题目描述

思路
题目当中给出了明确的信号"求给定字符串当中最长的回文子串",因此我们应该使用二维动态规划来解决这个问题。
为什么应该使用动态规划来解决这个问题?原因是假如我们对字符串进行暴力枚举,期间肯定会遇到很多回文子串的中间状态,在暴力枚举的过程中我们无法对中间状态进行记录(比如,从i...j这个子串构成一个回文子串,但下一次枚举无法复用了,需要重新统计一次)。使用一个二维数组来记录哪部分子串是回文子串可以大幅度降低我们求最长回文子串的时间复杂度,因为我们可以复用之前对回文子串统计的中间结果。
具体来说,我们声明一个dp bool 型二维数组,dp[i][j] == true的含义就是s[i:j]这个子串是回文子串。显然,初始情况下一定有dp[i][i] == true,原因是单个字符本身就是一个回文子串。接下来我们使用双重循环来对dp进行维护。我们在最外层循环枚举回文子串的长度currLen,在第二层循环使用i来遍历0..<n,也就是对字符串s进行遍历。显然有j == i + currLen - 1,如果s[i] == s[j],就说明s[i:j]可能构成一个回文子串。如果恰好有i == j - 1,则这两个相邻的字符构成一个子串;如果i < j - 1,那么令dp[i][j] = dp[i + 1][j - 1],如果s[i + 1][j - 1]是回文子串的话,那么dp[i + 1][j - 1] == true,就代表dp[i][j] == true,此时才构成一个回文子串。
在处理的过程中,我们在循环开始之前初始化start和ansLen,用于记录答案对应的最长回文子串的起点以及长度。如果dp[i][j] == true,且currLen > ansLen,则start, ansLen = i, currLen。
基于以上思路,我们编程解决这道问题。
Golang 代码
go
func longestPalindrome(s string) string {
n := len(s)
dp := make([][]bool, n + 1)
for i := 0; i <= n; i ++ {
dp[i] = make([]bool, n + 1)
dp[i][i] = true
}
start, ansLen := 0, 1
// 最长回文子串最坏情况下只包含一个字符, 因此 ansLen 至少是 1
for currLen := 2; currLen <= n; currLen ++ {
// 枚举子串的长度
for i := 0; i < n; i ++ {
j := i + currLen - 1
if j >= n {
break
}
if s[i] == s[j] {
if i == j - 1 {
dp[i][j] = true
} else {
dp[i][j] = dp[i + 1][j - 1]
}
}
if dp[i][j] && j - i + 1 > ansLen {
start = i
ansLen = currLen
}
}
}
return s[start: start + ansLen]
}