1、5. 最长回文子串
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为1000。
示例 1:
输入: "babad" 输出: "bab" 注意: "aba"也是一个有效答案。
示例 2:
输入: "cbbd" 输出: "bb"
(1)动态规划方法
回文字符串的子串也是回文,P[i][j](表示以i开始以j结束的子串)是回文字符串,那么P[i+1][j-1]也是回文字符串。该问题可以分解成一系列子问题。
定义状态方程和转移方程:
P[i][j]=0表示子串[i,j]不是回文串 P[i][j]=1表示子串[i,j]是回文串
dp[i][j] = (s[i] == s[j] && dp[i+1][j-1] == true);
class Solution { public: string longestPalindrome(string s) { int len = s.size(); if(len < 2) return s; vector<vector<bool>> dp(len, vector<bool>(len, false)); int start = 0, maxlen = 1; dp[0][0] = true; for (int i = 1; i < len; i++) { dp[i][i] = true; dp[i][i-1] = true; //这个初始化容易忽略,当k=2时要用到 } for(int k=2; k<=len; ++k) { // 枚举子串的长度 for(int i=0; i<=len-k; ++i) { // 枚举子串起始位置 int j = i+k-1; if(s[i] == s[j] && dp[i+1][j-1]) { dp[i][j] = true; start = i; // 记录回文子串的起点和长度 maxlen = k; } } } return s.substr(start, maxlen); } };
(2)中心扩展
以某个元素为中心,分别计算偶数长度的回文最大长度和奇数长度的回文最大长度。时间复杂度O(n^2),空间O(1)
中心扩展就是把给定的字符串的每一个字母当做中心,向两边扩展,这样来找最长的子回文串。算法复杂度为O(N^2)。
class Solution { public: string longestPalindrome(string s) { int len = s.size(); if(len < 2) return s; int start = 0, maxlen = 1; for(int i=1;i<len;++i) { int low = i-1, high = i; //寻找以i-1,i为中点偶数长度的回文 while(low>=0 && high<len && s[low]==s[high]) { low--; high++; } if(high-low-1 > maxlen) { maxlen = high-low-1; //应该是 high-low+1-2 = high-low-1 start = low + 1; } low = i-1, high = i+1; //寻找以i为中心的奇数长度的回文 while(low>=0 && high<len && s[low] == s[high]) { low--; high++; } if(high-low-1 > maxlen) { maxlen = high-low-1; start = low +1; } } return s.substr(start, maxlen); } };
2、516. 最长回文子序列
这道题给了我们一个字符串,让我们求最大的回文子序列,子序列和子字符串不同,不需要连续。而关于回文串的题之前也做了不少,处理方法上就是老老实实的两两比较吧。像这种有关极值的问题,最应该优先考虑的就是贪婪算法和动态规划,这道题显然使用DP更加合适。我们建立一个二维的DP数组,其中dp[i][j]表示[i,j]区间内的字符串的最长回文子序列,那么对于递推公式我们分析一下,如果s[i]==s[j],那么i和j就可以增加2个回文串的长度,我们知道中间dp[i + 1][j - 1]的值,那么其加上2就是dp[i][j]的值。如果s[i] != s[j],那么我们可以去掉i或j其中的一个字符,然后比较两种情况下所剩的字符串谁dp值大,就赋给dp[i][j],那么递推公式如下:
/ dp[i + 1][j - 1] + 2 if (s[i] == s[j])
dp[i][j] =
\ max(dp[i + 1][j], dp[i][j - 1]) if (s[i] != s[j])
对于任意字符串,如果头尾字符相同,那么字符串的最长子序列等于去掉首尾的字符串的最长子序列加上首尾;如果首尾字符不同,则最长子序列等于去掉头的字符串的最长子序列和去掉尾的字符串的最长子序列的较大者。
因此动态规划的状态转移方程为:
设字符串为str,长度为n,p[i][j]表示第i到第j个字符间的子序列的个数(i<=j),则:
状态初始条件:dp[i][i]=1 (i=0:n-1)
状态转移方程:dp[i][j]=dp[i+1][j-1] + 2 if(str[i]==str[j])
dp[i][j]=max(dp[i+1][j],dp[i][j-1]) if (str[i]!=str[j])
class Solution { public: int longestPalindromeSubseq(string s) { int n = s.size(); vector<vector<int>> dp(n, vector<int>(n)); for (int i = n - 1; i >= 0; --i) { dp[i][i] = 1; for (int j = i + 1; j < n; ++j) { if (s[i] == s[j]) { dp[i][j] = dp[i + 1][j - 1] + 2; } else { dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]); } } } return dp[0][n - 1]; } };