题目
给你一个字符串 s,找到 s 中最长的回文子串。
示例 1:
输入: s = "babad"
输出: "bab"
解释: "aba" 同样是符合题意的答案。
来源:力扣(LeetCode)
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
代码
动态规划
public class Leetcode5 {
public String longestPalindrome(String s) {
if (null == s || s.length() < 2) {
return s;
}
int left = 0;
// 长度至少为1
int max = 1;
int len = s.length();
boolean dp[][] = new boolean[len][len];
for (int i = 0; i < len; i++) {
// 长度为1时都是回文串
dp[i][i] = true;
}
// 遍历长度:从长度短的往长的演进
for (int i = 2; i <= len; i++) {
//遍历对应长度下的左指针
for (int j = 0; j < len; j++) {
int right = j + i - 1;
if (right >= len) {
// 越界
continue;
}
//保证至少是(2,4)的距离
boolean temp = s.charAt(j) == s.charAt(right);
if (right - j <= 2) {
dp[j][right] = temp;
} else {
dp[j][right] = dp[j + 1][right - 1] && temp;
}
if (dp[j][right] && right - j + 1 > max) {
max = right - j + 1;
left = j;
}
}
}
return s.substring(left, left + max);
}
}
中心扩展
class Solution {
public String longestPalindrome(String s) {
if (s == null || s.length() < 1) {
return "";
}
int start = 0, end = 0;
for (int i = 0; i < s.length(); i++) {
int len1 = expandAroundCenter(s, i, i);
int len2 = expandAroundCenter(s, i, i + 1);
int len = Math.max(len1, len2);
if (len > end - start) {
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substring(start, end + 1);
}
public int expandAroundCenter(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
--left;
++right;
}
return right - left - 1;
}
}
解析
动态规划
-
为什么要用动态规划?
题目是要求最大回文子串,而回文的定义就是左右对称;也就意味着,当我们把左右各自去掉一个字符后,还是回文子串;同时也意味着,当有一个回文子串后,在左右两边各加一个字符,如何两个字符一样,组成的新的子串也是回文子串。于是可以得出推导公式 boolean[i][j] = boolean[i+1][j-1]&&(char[i]==char[j])
-
该题目动态规划的推导公式是什么?
- 当长度为1时,肯定是回文,即dp[i][i]=true
- 当长度<=3;即j-i<=2时,看两边的字符是否一样即可,即boolean[i][j] = (char[i]==char[j])
- 其他情况:boolean[i][j] = boolean[i+1][j-1]&&(char[i]==char[j])
-
个人觉得动态规划的难点:如何遍历,来达到以小求大的效果
从咱们前面的推导公式来看, boolean[i][j] = boolean[i+1][j-1]&&(char[i]==char[j]);其实本质就是把长度大的缩小长度小点来计算;所以第一层循环以长度来遍历,因为长度为1的结果可以预置,直接从len=2开始;而长度确定以后,下一步要确定左右边界,所以第二层以左边界遍历,左边界确定后,右边界则可以计算出;也就可以走后面的结果
中心扩展
这个方法就比较简单些,其实本质就是遍历每种回文串的可能中心点,然后从中心点一直往外扩散;知道遇到不是回文串为止,然后记录最大值即可
需要注意的一点就是,回文串的中心点可能是空,即偶数个字符;所以扩展时有两种公式,需要取最大的那个,即:
int len1 = expandAroundCenter(s, i, i);
int len2 = expandAroundCenter(s, i, i + 1);
总结
- 中心扩展是这个题目比较适合也比较简单的方法,穷举所有的中心点,然后一直往外扩散
- 而这是也是动态规划的典型题目;类似的题目都是可以按照这种动态规划思维去做;主要是满足能从小推大,然后主要要确定推导公式和遍历目标