1.题目要求
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。 回文的意思就是:正着读反着读都一样,是对称的
2.题目示例
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
输入: "cbbd"
输出: "bb"
3.提示
How can we reuse a previously computed palindrome to compute a larger palindrome? 我们如何重用一个先前计算的回文来计算一个更大的回文? If “aba” is a palindrome, is “xabax” and palindrome? Similarly is “xabay” a palindrome? 如果“aba”是一个回文,“xabax”和回文吗?同样,“xabay”也是回文吗? Complexity based hint: If we use brute-force and check whether for every start and end position a substring is a palindrome we have O(n^2) start - end pairs and O(n) palindromic checks. Can we reduce the time for palindromic checks to O(1) by reusing some previous computation. 基于复杂性的提示: 如果我们使用蛮力检查每一个开始和结束位置的子字符串是否是一个回文,我们有O(n^2)开始-结束对和O(n)回文检查。我们能否通过重用以前的一些计算来将回文检查的时间减少到O(1) ?
4.解题
4.1 解题思路
首先读题过后,我们能想到的是,就一个子串来说,如果它是一个回文串(长度大于2),那么将它的首尾字母去除后,它依然是一个回文串。例如:“cbcbc”,在已知中间子串(“bcb”)是回文串的情况下,判断首尾字符串相等后,就可以确定“cbcbc”一定是回文串 根据这样的思路,我们可以用动态规划的方法解决问题,我们用P(i,j)表示字符串s的第i到j个字母组成的串是否为回文串 P(i,j)等于true的话就可以判断子串Si~Sj是不是回文串,等于false的话包含两种可能性,第一个就是s[i,j]本身不是一个回文串,第二个就是i>j,此时s[i,j]本身不合法 那么我们就可以得到动态规划的状态转移方程: P(i,j)=P(i+1,j-1)∧(Si==Sj) 也就是说,只有s[i+1:j-1]是回文串,并且s的第i和j个字母相同时,s[i:j]才会是回文串 上文的所有讨论是建立在子串长度大于2的前提之上的,我们还需要考虑边界条件,即子串长度为1或2的情况。对于长度为1的情况来说,它肯定是一个回文串,对于长度为2的情况来说,只要它们字母相同肯定是一个回文串。因此我们就可以给出动态规划的边界条件:P(i,i)=true和P(i,i+1)=(Si==Si+1) 以上就已经考虑完所有的情况了,最终的答案为所有P(i,j)=true中j-i+1(回文子串长度)的最大值 注意:在状态转移方程中,我们是从长度较短的字符串向长度较长的字符串进行转移的,因此一定要注意动态规划的循环顺序
4.2 业务代码
class Solution {
public String longestPalindrome ( String s) {
int len = s. length ( ) ;
if ( len < 2 ) {
return s;
}
int maxLen = 1 ;
int begin = 0 ;
boolean [ ] [ ] dp = new boolean [ len] [ len] ;
for ( int i = 0 ; i < len; i++ ) {
dp[ i] [ i] = true ;
}
char [ ] chars = s. toCharArray ( ) ;
for ( int j = 1 ; j < len; j++ ) {
for ( int i = 0 ; i < j; i++ ) {
if ( chars[ i] != chars[ j] ) {
dp[ i] [ j] = false ;
} else {
if ( j - i < 3 ) {
dp[ i] [ j] = true ;
} else {
dp[ i] [ j] = dp[ i + 1 ] [ j - 1 ] ;
}
}
if ( dp[ i] [ j] && j - i + 1 > maxLen) {
maxLen = j - i + 1 ;
begin = i;
}
}
}
return s. substring ( begin, begin + maxLen) ;
}
}
4.3 运行结果
5.优化
5.1 优化思路
其实这道题可以使用暴力枚举来做出来,就是枚举所有长度严格大于1的子串,然后判断子串是否是回文串(过于简单暴力就不演示了),这时回文串的枚举可以从两边开始也可以从中间开始,这时第二种思路就有了眉目。 也就是中心扩散法,通过枚举所有可能子串的中心位置,然后从内向外依次判断是不是回文子串。 这时就需要判断回文子串是奇数还是偶数,因为技术回文子串的中心位置只有一个字符,偶数回文子串中心有两个字符 同时在枚举结束后,要记录子串相关变量
5.2 优化业务代码
class Solution {
public String longestPalindrome ( String s) {
if ( s == null || s. length ( ) < 1 ) {
return "" ;
}
int start = 0 ;
int end = 0 ;
for ( int i = 0 ; i < s. length ( ) ; i++ ) {
int len_odd = expandCenter ( s, i, i) ;
int len_even = expandCenter ( s, i, i + 1 ) ;
int len = Math. max ( len_odd, len_even) ;
if ( len > end - start) {
start = i - ( len - 1 ) / 2 ;
end = i + len/ 2 ;
}
}
return s. substring ( start, end + 1 ) ;
}
private int expandCenter ( String s, int left, int right) {
while ( left >= 0 && right < s. length ( ) && s. charAt ( left) == s. charAt ( right) ) {
left-- ;
right++ ;
}
return right - left - 1 ;
}
}
5.3 优化结果
6.总结
因为动态规划的时间空间复杂度都是O(n2 ),而中心扩散法的空间复杂度只有O(1),虽然时间复杂度还是是O(n2 ),但是优化出来还是进不来不少。大家还有什么更好的方案欢迎在下面留言哟!