这是我参与更文挑战的第5天,活动详情查看: 更文挑战
第5天,继续挑战第5题。这题似曾相识,跟最长公共子串非常像,一样是使用动态规划的方法。学生时代刚学算法,觉得动态规划是最难的,后来做的多了,相似的题目也能举一反三,不过其实自己知道还没有深入的理解,遇到题目再慢慢深入吧。
题目
给你一个字符串 s,找到 s 中最长的回文子串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd"
输出:"bb"
示例 3:
输入:s = "a"
输出:"a"
示例 4:
输入:s = "ac"
输出:"a"
思路
之前也说过,一般遇到算法题,先想想如果不考虑时间复杂度和空间复杂度,怎么样找到结果,理解题目意思后,然后再看看如何优化时间复杂度和空间复杂度。这题暴力的解法,就是遍历每个子串,然后判断每个子串是不是回文。遍历每个子串的时间复杂度是O(n^2),判断每个子串的时间复杂度是O(n),所以整体来说,时间复杂度为O(n^3);空间复杂度的话,不需要存下每个子串,只要存当前子串和最长回文子串,空间复杂度为O(n)。比较下来,时间复杂度是我们这题需要解决的问题。
之前说过,这题起始跟最长公共子串非常像。这里判断子串这一步可以优化,因为可以利用之前判断的结果。我们判断一个子串S(start)~S(end)是否是回文,其实必须满足2个条件:1、S(start)=S(end),2、S(start+1)~S(end-1)是回文,即判断S(start)~S(end)的时候,可以利用之前判断的S(start+1)~S(end-1)的结果。
如果我们从子串长度的从小到大来遍历,把每个子串是否是回文的结果存下来,那么每次判断子串是否是回文,时间复杂度O(1),因为遍历所有子串还是必不可少,整体的时间复杂度就是O(n^2);因为这里要把每个子串是否是回文存下来,所以空间复杂度就是O(n^2),所以这个方法是用增加空间复杂度去换取了减少时间复杂度,这个思想在动态规划的题目中经常使用到。
注意点
上面说的方法,成立的前提条件是子串长度>=2,所以对于长度为1和2的情况,需要单独讨论一下。长度为1时,显然子串可以直接认定为回文;长度为2时,只要满足S(start)=S(end)就可以认为是回文,子串S(start+1)~S(end-1)并不存在
Java版本代码
class Solution {
public String longestPalindrome(String s) {
int length = s.length();
// 代表从start到end的字串是否是回文,因为end>=start,从形状上说,这是一个在右上角的三角形而不是正方形
boolean[][] dp = new boolean[length][length];
int maxLen = 0;
int maxStart = 0;
// 用长度进行for,计算到较大长度的子串时,长度较短的子串已经被计算过了
for (int len = 1; len <= length; len++) {
for (int start = 0; start < length; start++) {
int end = start + len - 1;
if (end >= length) {
break;
}
if (start == end) {
// 相等时,必然是回文
dp[start][end] = true;
} else if (end - start == 1) {
// 相邻时,是否回文取决于是否相等
dp[start][end] = s.charAt(start) == s.charAt(end);
} else {
// 其他情况下,取决于start和end位置上字符是否相等且dp[start+1][end-1]
dp[start][end] = (s.charAt(start) == s.charAt(end)) && dp[start+1][end-1];
}
if (dp[start][end] && len > maxLen) {
maxLen = len;
maxStart = start;
}
}
}
return s.substring(maxStart, maxStart + maxLen);
}
}