算法小白,最近刷LeetCode。希望能够结合自己的思考和别人优秀的代码,对题目和解法进行更加清晰详细的解释,供大家参考^_^
5. Longest Palindromic Substring
Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.
Example:
Input: "babad"
Output: "bab"
Note: “aba” is also a valid answer.
Example:
Input: "cbbd"
Output: "bb"
题目让找出最长的回文子串。这道题一开始只能想到枚举所有区间,再进行回文判断的方式,这样的方法肯定是会超时的,最后看了题解,说是要用动态规划。
本题的动规思路是,如果区间[i, j] (i<=j) 构成一个回文串,那么区间[i-1, j+1]构成回文串的充要条件是 str[i-1]==str[j+1], 因此基本的递推公式为:
dp[i, j] = dp[i+1, j-1] && str[i+1] == str[j-1]
dp[i, j]是bool型,表示区间 [i, j] 构成的子串是否是个回文串,显然这里 i<=j
注:这里使用的是左闭右闭区间,C++规范一般要求使用左闭右开区间
有了递推公式,接下来就得考虑初值了,很显然,dp[i, i] (i=0…n)是等于true的,但只有这一类的初值明显不够,无法完成递推计算,还需要d[i, i+1] 这类的初值,显然,d[i, i+1] = (str[i] == str[i+1])
OK, 递推公式和初值都有了,接下来就可以码代码了。
class Solution {
public:
bool dp[1000+5][1000+5];
string longestPalindrome(string s) {
int len = s.length();
if (len == 1) return s;
if (len == 2) {
if (s[0] == s[1]) return s;
else return s.substr(0, 1);
} // 特殊情况直接返回
int beg = 0, end = 0;
int maxlen = 1;
memset(dp, false, sizeof(dp));
// 生成初值,即dp[i,i]和dp[i,i+1]
for (int i = 0; i < len; ++i){
dp[i][i] = true;
if (i+1 < len) {
dp[i][i+1] = (s[i] == s[i+1]);
if (dp[i][i+1]) { beg = i; end = i + 1; maxlen = 2; }
}
}
// 利用递推公式,同时更新最大长度和对应的区间下标
for (int i = 2; i < len; ++i){
for (int j = 0; j+i < len; ++j){
dp[j][j+i] = ( dp[j+1][j+i-1] && (s[j] == s[j+i]) );
if (dp[j][j+i] && i+1 > maxlen){
beg = j; end = j + i; maxlen = i+1;
}
}
}
return s.substr(beg, maxlen);
}
};
将dp看做一个方阵,从递推公式中,我们可以观察到,dp[i, j] 的值只与其左下角的值dp[i+1, j-1] 有关,因此,从空间上来说,并没有必要记录所有的 i, j 组合,在更新时完全可以覆盖后续不再用到的空间,因此,只需要一个两行的数组就可以满足要求。
另一点要注意的时,由于更新时要使用左下角即靠前的值,因此要进行倒序更新,代码如下:
class Solution {
public:
bool dp[2][1000+5]; // 使用两行就够了
string longestPalindrome(string s) {
int len = s.length();
if (len == 1) return s;
if (len == 2) {
if (s[0] == s[1]) return s;
else return s.substr(0, 1);
}
int beg = 0, end = 0;
int maxlen = 1;
memset(dp, true, sizeof(dp)); // 主要是给第一行赋初值,即dp[i][i],全部为true
// 第二行进行手动更新,对应之前代码中的dp[i, i+1]
for (int i = len - 1; i - 1 >= 0; --i){
dp[1][i] = (s[i] == s[i-1]);
if (dp[1][i]) {
beg = i-1; end = i; maxlen = 2;
}
}
for (int i = 2; i < len; ++i){
for (int j = len - 1; j - i >= 0; --j) { // 进行倒序更新,覆盖不再用到的值
dp[i%2][j] = (dp[(i%2)][j-1] && (s[j] == s[j-i]));
if (dp[i%2][j] && i + 1 > maxlen){
beg = j - i; end = j; maxlen = i+1; }
}
}
return s.substr(beg, maxlen);
}
};
上述方法的时间复杂度都是O(n^2),提交后也只是打败了20%的小伙伴。
还有一种中心扩展的方法,不过他的时间复杂度也是O(n^2),这里先不介绍了,题解里面说,还有一种O(n)的牛X方法,叫做Manacher算法,等以后有机会再慢慢研究