leetcode5. 最长回文子串
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
示例 2:
输入: “cbbd”
输出: “bb”
动态规划的思路
一般题里问什么就把什么设成dp。
在当前状态,假设前面的状态全部计算出来了,即无后效性,再去寻找动态规划方程。
dp [i][j]表示字符串i~j是不是回文字符串。
初始化:dp[i][i]肯定是回文的,因为只有一个字符。
当遍历到一个状态的时候,这个状态是不是回文取决于两个:
- 字符 i 和字符 j 相等,也就是说字符串的首尾是一样的。
- 去掉首尾,也就是 i+1~j-1 是回文字符串。
- 字符串长度为2,也就是说去掉首尾就没了,类似于 “bb” 这种需要特殊判断。
思路就绪了,开始写代码。
先看我的错误版本:
public String longestPalindrome(String s) {
int n = s.length();
int[][] dp = new int[n][n];//i~j 是否为回文,并且长度是多少
for (int i = 0; i < n; i++) {
dp[i][i] = 1;
}
String ans = new String(s.toCharArray(), 0, 1);
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (i + 1 == j && s.charAt(i) == s.charAt(j)) {
dp[i][j] = 2;
}
if (dp[i + 1][j - 1] > 0 && s.charAt(i) == s.charAt(j)) {
dp[i][j] = dp[i + 1][j - 1] + 2;
}
if (dp[i][j] > ans.length()) {
ans = new String(s.toCharArray(), i, j - i + 1);//截取字符串
}
System.out.println("i = "+i+" j = "+j+" ans = "+ans+" dp[i][j] = "+dp[i][j]);
}
}
return ans;
}
乍一看不会出现问题,但是对于 “babab” ,结果是错误的,只能得到 “bab” 。来看原因。
把dp数组画出来
因为动态规划其实就是怎么填充dp数组,我们看看dp数组是怎么变化的。
初始状态:
这是在初始化之后的初始状态。
看看上面的代码是怎么填充dp数组的。
当 i=0,j=4 时,需要用到 dp[1][3] 。这是不对的,动态规划需要有无后效性,也就是说当前的状态不能用到之后的状态。
那么只能换一种填表方式。填表方式有很多,只要满足当前的状态不用到没填过的就行。我用的是下面这种。
这回就ok了,来看代码。优化了一部分,没必要每次截取,只需记录位置就行。
public String longestPalindrome(String s) {
if (s.length() == 0) return s;
int n = s.length();
int[][] dp = new int[n][n];//i~j 是否为回文,并且长度是多少
for (int i = 0; i < n; i++) {
dp[i][i] = 1;
}
int left = 0;
int right = 0;
int max = 0;
for (int i = n - 1; i >= 0; i--) {
for (int j = n - 1; j > i; j--) {
if (i + 1 == j && s.charAt(i) == s.charAt(j)) {
dp[i][j] = 2;
}
if (dp[i + 1][j - 1] > 0 && s.charAt(i) == s.charAt(j)) {
dp[i][j] = dp[i + 1][j - 1] + 2;
}
if (dp[i][j] > max) {
max = dp[i][j];
left = i;
right = j;
}
//System.out.println("i = "+i+" j = "+j+" ans = "+ans+" dp[i][j] = "+dp[i][j]);
}
}
return s.substring(left, right + 1);
}
这回动态规划的理解更进一步了。
leetcode上面类似的字符串题:
字符串三连
最长回文子序列
回文子串
最长回文子串
leetcode 75/100