最近碰到一个求解最长回文子串的问题,即给定一个字符串s,需要找到s中最长的回文子串。例如:
- 输入: “babad”
- 输出: “bab” (注意”aba”也是一个有效答案)
这个题目的解法有多种。一般来说比较流行的是 动态规划 或者 最长公共子串 法。
动态规划
首先讲一下动态规划的方法。这里主要是利用回文数的性质来做的。给出以下定义:
$$
P(i,j) =
\begin{cases}
true & 如果 s[i,j] 是回文子串 \\
false & 如果 s[i,j]不是回文子串
\end{cases}
$$
那么利用动态规划,我们可以得到状态转移式:
$$
P(i,j) = (P(i+1,j-1) \&\& (S_i == S_j))
$$
所以在求解最长回文子串的过程中,我们只需要维护类型为boolean
的dp[i][j]
数组,其意义如下:
$$
boolean[i][j] =
\begin{cases}
true & 如果s[i:j]是回文串 \\
false & 如果s[i:j]不是回文串
\end{cases}
$$
初始化该数组后,我们依次对长度为1,2,…,len-1长度的子串进行遍历,填充boolean数组,并且在填充的过程中,记录最长子串的位置即可。具体来说:
- 遍历长度为1的子串,即
boolean[i][i]
,全部初始化为true。 - 遍历长度为2的子串,
boolean[i][i+1] = (s[i] == s[i+1])
. - 依次遍历长度为3,4,…,len-1的子串,利用动态规划的性质。
遍历完毕后,即得到最长回文子串的位置。
下面分析该方法的时间复杂度和空间复杂度:
- 时间复杂度:两层遍历,外层长度为n,内层依次为n, n-1, n-1, …, 1. 故时间复杂度为$O(n^2)$.
- 空间复杂度:维护数组
dp[n][n]
,故空间复杂度为$O(n^2)$.
下面附上代码:
1 | public String longestPalindrome(String s) { |
中心扩展
中心扩展的方法,同样利用了动态规划的思想,但是在其基础上,进行了改进。
中心扩展的思路如下:
- 对于长度为n的字符串s,找到其2n个中点。
- 对每一个中点,往两边扩展找到最长的回文子串。
- 所有中点遍历完后,即找到最长的回文子串。
注意:有2n个中点是因为回文子串既有偶数也有奇数形式,对于奇数形式我们定义其中点为中心字符,对于偶数形式我们定义其中点为中心两个字符之间的中点。
用java
对以上方法进行实现,得到以下代码:
1 | public class LongestPalindrome { |
后缀树
以下是由字符串ABAB, BABA, ABBA
组成的广义后缀树。
下面对后缀树进行一个解释。