赞
踩
文章链接:647.回文子串
视频链接:动态规划,字符串性质决定了DP数组的定义 | LeetCode:647.回文子串
状态:第一反应必须得是双指针,然后再是到动态规划,为了能够顺利解答最长回文子串,本题一定也要掌握动态规划。
其实回文子串类题目的动规思路,也是照着双指针来的
其实子串问题和子序列问题非常类似,也是存在最优子结构,那就意味着原问题的最优解可以由子问题的最优解推导出来。
其实回文的问题很容易联想到双指针。不过为了后续能够解决516.最长回文子序列问题,我们还是先使用动规解决一下该问题,为后面打基础。
首先我们必须明确,回文子串就是以一个字符为中心(基数长度的回文),也可以是以两个相邻字符之间的位置为中心(偶数长度的回文)。
所以我们可以考虑以字符串中的每一个字符为中心,然后尝试扩展这个中心来查找可能的回文子串。
enpandAroundCenter
,该函数接受字符串和两个指针位置,然后向两边扩展,直到不再形成回文,没形成一个回文,计数器就增加std::function<void(int, int)> expandAroundCenter = [&](int left, int right) {
while(left >= 0 && right < n && s[left] == s[right]) {
ans++;
left--;
right++;
}
};
for (int i = 0; i < n; i++){
expandAroundCenter(i, i);
if (i + 1 < n) expandAroundCenter(i, i + 1);
}
class Solution { public: int countSubstrings(string s) { int n = s.length(); int ans = 0; // 使用lambda表达式来进行函数定义 // 可以用aotu 代替 std::function std::function<void(int, int)> expandAroundCenter = [&](int left, int right) { while (left >= 0 && right < n && s[left] == s[right]) { ans++; // Count this palindrome left--; // Expand to the left right++; // Expand to the right } }; for (int i = 0; i < n; i++) { // 检查偶数位置 expandAroundCenter(i, i); // 检查奇数位置 if (i + 1 < n) { expandAroundCenter(i, i + 1); } } return ans; } };
我们回文子串的问题并不能像解决子序列问题那样去设置dp数组。
如果我们定义,dp[i] 为 下标i结尾的字符串有 dp[i]个回文串的话,我们会发现很难找到递归关系,因为根本就无法跟相邻数组元素有什么关系。联想到使用双指针解决回文子串,我们的dp数组设置是不是也能参考设计呢?
最好的解决方法设置:布尔类型的dp[i][j]
:表示区间范围[i,j]
(注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]
为true,否则为false。
两种情况:s[i]与s[j]相等,s[i]与s[j]不相等,其中相等时又分为三种情况
如果不相等,那dp[i][j]=false
如果相等:
i
与 j
相差大于1的时候,例如cabac
,此时s[i]
与s[j]
已经相同了,我们看i
到j
区间是不是回文子串就看aba
是不是回文就可以了,那么aba
的区间就是 i+1
与 j-1
区间,这个区间是不是回文就看dp[i + 1][j - 1]
是否为true。综上所述:
if (s[i] == s[j]) {
if (j - i <= 1) { // 情况一 和 情况二
result++;
dp[i][j] = true;
} else if (dp[i + 1][j - 1]) { // 情况三
result++;
dp[i][j] = true;
}
}
全是设置为false
二维的递推顺序最好就是根据递推公式画一个格子:
我们需要首先知道左下角的格子,才能推导出我们的dp,
所以一定要从下到上,从左到右遍历,这样保证dp[i + 1][j - 1]
都是经过计算的。
输入:“aaa”,dp[i][j]
状态如下:
图片中有6个true,所以就有六个回文子串
这里的两个代码技巧都是要掌握的,我第一次写的时候,甚至判断了s[i] != s[j]
,成功写出一坨。
//完整代码 class Solution { public: int countSubstrings(string s) { vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false)); int result = 0; for (int i = s.size() - 1; i >= 0; i--) { // 注意遍历顺序 for (int j = i; j < s.size(); j++) { if (s[i] == s[j]) { if (j - i <= 1) { // 情况一 和 情况二 result++; dp[i][j] = true; } else if (dp[i + 1][j - 1]) { // 情况三 result++; dp[i][j] = true; } } } } return result; } }; //简洁版代码 class Solution { public: int countSubstrings(string s) { vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false)); int result = 0; for (int i = s.size() - 1; i >= 0; i--) { for (int j = i; j < s.size(); j++) { if (s[i] == s[j] && (j - i <= 1 || dp[i + 1][j - 1])) { result++; dp[i][j] = true; } } } return result; } };
文章链接:516最长回文子序列
视频链接:动态规划再显神通,LeetCode:516.最长回文子序列
状态:有点思维惯性了,延续上文的true or false给我整懵了,其实子序列的题目设置dp数组基本都是一个套路,基本的本题遇上一题的相同点在于,基本的判断思路是延续的。
对于这个问题,使用双指针并不是最有效的方法。因为最长回文子序列不要求是连续的字符,而是可以跳过某些字符形成回文。本题中,回文子序列与回文子串是有区别的。
dp[i][j]
:字符串s
在[i, j]
范围内最长的回文子序列的长度为dp[i][j]
在判断回文子串的题目中,关键逻辑就是看s[i]与s[j]是否相同。
如果s[i]
与s[j]
相同,那么dp[i][j] = dp[i + 1][j - 1] + 2;
具体如图所示:
如果s[i]与s[j]不相同,说明s[i]和s[j]的同时加入 并不能增加[i,j]区间回文子序列的长度,那么分别加入s[i]、s[j]看看哪一个可以组成最长的回文子序列。
加入s[j]的回文子序列长度为dp[i + 1][j]
。
加入s[i]的回文子序列长度为dp[i][j - 1]
。
那么dp[i][j]
一定是取最大的,即:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
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]);
}
从递推公式可以看出,递推公式计算不到i 和j相同时候的情况。
当i与j相同,那么dp[i][j]
一定是等于1的,即:一个字符的回文子序列长度就是1。
其他都应该初始化成0,这样题对公式dp[i][j]=max(dp[i + 1][j], dp[i][j - 1]);
才不会被初始值覆盖
vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
for (int i = 0; i < s.size(); i++) dp[i][i] = 1;
从递归公式中,可以看出,dp[i][j]
依赖于 dp[i + 1][j - 1]
,dp[i + 1][j]
和 dp[i][j - 1]
,如图:
所以当我们遍历i的时候是从上到下,然后j是从左到右
for (int i = s.size() - 1; i >= 0; i--) {
for (int j = i + 1; j < s.size(); j++) {
...
}
}
输入s:“cbbd” 为例,dp数组状态如图:
红色框即:dp[0][s.size() - 1]
; 为最终结果。
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][s.size() - 1]; } };
二刷回来整总结!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。