赞
踩
如果用暴力求解,两层for循环加一层判断,两个遍历指针i和j构成一个区间,每次判断这个区间内的字符串是否为回文串,这样的求法时间复杂度为O(n^3)。这里使用动态规划可以将判断i和j区间的字符串是否为回文串的时间复杂度降为O(1).
如上图所示,如果使用暴力解法,那么需要遍历[i,j]区间中的每个字符,但使用动态规划就不需要遍历这个区间内的所有字符了,只需要判断s[i]和s[j]是否相等,前提是要先记录[i+1,j-1]区间内的情况,因此需要占用额外的空间,也就是用空间换取时间的方法。因为判断[i,j]区间的字符串是否为回文串就需要先知道[i+1,j-1]区间内的字符串是否为回文串,因此整个区间要先从最右边开始“扩张”,即i从最右边开始向左移动,j指针从i开始向右移动。
确定dp数组下标及其含义
dp[i][j]:如果[i,j]区间内的字符串为回文串,则dp[i][j]为true,否则为false。
确定递推公式
综上所述,递推公式的代码实现为:
if(s[i] != s[j])
dp[i][j] = false;
else{
if(i == j || i + 1 == j || ((i + 1 < j) && dp[i+1][j-1] == true)){ //三种为true的情况
dp[i][j] = true;
result ++;
}
else
dp[i][j] = false;
}
确定遍历顺序
从之前的分析就提到,[i,j]区间内字符串的判断可能要依赖[i+1,j-1]区间的判断结果,因此整个区间必定是要从最右边开始向左移动,因此i从大到小,j从i开始递增。
初始化dp数组
根据递推公式,似乎需要初始化dp数组,但其实可以不初始化,因为只有[i,j]区间内的元素多于两个时候才需要用到dp[i+1][j-1],当[i,j]区间只有一个或两个元素的时候直接就能得到结论,因此可以看作初始化过程已经包含在遍历过程中了,因此不需要额外初始化。
举例推导dp数组
可以看出,因为i <= j,因此dp数组只用到了对角线及右上半部分,图中有6个true,因此有6个回文子串。
完整的代码实现如下:
class Solution { public: int countSubstrings(string s) { int result = 0; vector<vector<bool>> dp(s.size(), vector<bool>(s.size())); for(int i = s.size(); i >= 0; i--){ for(int j = i; j < s.size(); j++){ if(s[i] != s[j]) dp[i][j] = false; else{ if(i == j || i + 1 == j || ((i + 1 < j) && dp[i+1][j-1] == true)){ //三种为true的情况 dp[i][j] = true; result ++; } else dp[i][j] = false; } } } return result; } };
确定dp数组下标及其含义
dp[i][j]:字符串s中[i,j]区间构成的字符串中的最长回文子串的长度为dp[i][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]);
综上所述,dp数组的初始化如下图所示(其余不用初始化):
为了方便,其余都初始化为0。
class Solution { public: int longestPalindromeSubseq(string s) { vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0)); for(int i = 0; i < s.size(); i++) dp[i][i] = 1; for(int i = s.size()-1; i >= 0; i--){ for(int j = i + 1; j < s.size(); 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].back(); } };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。