题目链接
这道题我们采用动态规划的解法,倒不是动态规划的解法对于这道题有多好,它并不是最优解。但是,这道题的动态规划思想是非常有用的,我们使用这道题的动态规划思想,可以让一些hard题变为easy题。
也就是说,这道题的动态规划思想其实就是起到了一个抛砖引玉的作用。
如何表示出所有的子串的情况?可以用 i 表示某个子串的起始位置,用 j 来表示某个子串的末尾位置,暴力枚举,可以在N^2的时间复杂度内求出所有子串是否为回文子串。
所以,我们用二维dp[i][j]表来表示,以 i 位置为起始位置且以 j 位置为结尾的子串是否为回文子串。如果为回文子串那么dp[i][j]为true,否则为false。(我们人为规定 i <= j)
我们要知道dp[i][j]为是否为回文子串,首先要判断 s[i] 是否等于 s[j]。
如果 s[i] != s[j],那么不管 i 和 j 中间的元素序列是怎样的,以 i 位置为起始位置,以 j 位置为终止位置的子串一定不为回文子串。
如果 s[i] == s[j],那么需要对 i 和 j 的位置进行判断。
由于我们求dp[i][j]的时候,需要用到 dp[i+1][j-1],且 i 的循环为外层的循环,所以让 i 从大到小循环即可。
class Solution {
public:
int countSubstrings(string s) {
int n = s.size();
// 创建二维dp表,dp表中每个位置的初始值为false
vector<vector<bool>> dp(n, vector<bool>(n));
int ret = 0; // 用于保存有多少位true的dp位置,即有多少个回文子串
// 在循环时 i 从大到小进行循环
for (int i = n - 1; i >= 0; --i)
{
// j的循环顺序其实无所谓,只要循环的区间在[i, n)即可
for (int j = i; j < n; ++j)
{
// 根据状态转移方程求dp[i][j]
if (s[i] == s[j])
dp[i][j] = i + 1 < j ? dp[i+1][j-1] : true;
// 如果dp[i][j]为true,增加ret
if (dp[i][j]) ++ret;
}
}
return ret;
}
};
题目链接
与求回文子串思路差别不大
它也就是求出每一个回文子串后,不是统计有多少个回文子串,而是挑出最长的那个回文子串并在循环结束之后返回即可。
代码也只不过改动了一点点而已。
class Solution {
public:
string longestPalindrome(string s) {
int n = s.size();
vector<vector<bool>> dp(n, vector<bool>(n));
int start = 0; // 最长的回文子串的起始位置
int len = 0; // 最长的回文子串的长度
for (int i = n - 1; i >= 0; --i)
{
for (int j = i; j < n; ++j)
{
if (s[i] == s[j])
dp[i][j] = i + 1 < j ? dp[i+1][j-1] : true;
// 如果该位置为回文子串,那么判断长度是否大于之前最长的长度
// 如果大于,则对起始位置和最长长度进行更新
if (dp[i][j] == true && j - i + 1 > len)
{
len = j - i + 1;
start = i;
}
}
}
// 根据起始位置和长度返回最长回文子串
return s.substr(start, len);
}
};
这道题我们使用动态规划的方法来解,首先创建一个大小为字符串长度的dp表。dp[i] 表示 s[0, i] 的字符串最小划分多少次可以全划分为回文串。
求状态转移方程,我们要考虑两种情况。s[0, i] 的字符串是回文串和不是回文串的情况。
注意,这里假设我们已经知道了哪段字符串是不是回文串,至于是如何知道的后面会说。
这个通过上面的题解也就能知道了。
class Solution {
public:
int minCut(string s) {
int n = s.size();
// 求出所有子串是否为回文串
vector<vector<bool>> isPal(n, vector<bool>(n));
for (int i = n - 1; i >= 0; --i)
for (int j = i; j < n; ++j)
if (s[i] == s[j])
isPal[i][j] = i + 1 < j ? isPal[i+1][j-1] : true;
// 创建dp表,由于是求最小值,可以先将所有位置初始化为最大
vector<int> dp(n, INT_MAX);
for (int i = 0; i < n; ++i)
{
if (isPal[0][i]) dp[i] = 0;
else
{
for (int j = 1; j <= i; ++j)
if (isPal[j][i]) dp[i] = min(dp[i], dp[j-1] + 1);
}
}
return dp[n-1];
}
};
此题采用动态规划的方法,创建一个二维dp表,dp[i][j]表示s[i, j]中最大回文子序列的长度。且我们人为规定 i 是一定小于等于 j 的。
在求dp[i][j]时,首先要判断s[i]和s[j]是否相同。
如果 s[i] == s[j]
如果s[i] != s[j]
那么此时,说明不能同时以 i 为开头和以 j 为结尾,我们去掉这种情况寻找一个最大子序列即可。方法就是在 dp[i+1, j] 和 dp[i, j-1] 中选一个最大的即可。即dp[i][j] = max(dp[i+1[j], dp[i][j-1]);
在求dp[i][j]的时候,我们可能会用到 i + 1 和 j - 1,在它们有可能越界的时候,一定是 i 等于 j 的时候。我们创建的dp表是二维的,我们可以想到,在可能越界的时候,就是左上角的位置或者右下角的位置,但其实这两个位置满足 i == j,那么dp[i][j] 就会被直接赋值为1,此时就不会用到 i + 1 和 j - 1 了,所以其实我们不用考虑越界的情况。
class Solution {
public:
int longestPalindromeSubseq(string s) {
int n = s.size();
// 创建二维dp表,dp[i][j]表示s[i, j]最大子序列的长度
vector<vector<int>> dp(n, vector<int>(n));
// dp[i][j]需要用到dp[i+1][j-1]
// 所以i从大到小循环,j从小到大循环,且i是小于等于j的
for (int j = 0; j < n; ++j)
{
for (int i = j; i >= 0; --i)
{
if (s[i] == s[j])
{
if (i == j) dp[i][j] = 1;
else if (i + 1 == j) dp[i][j] = 2;
else 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];
}
};
题目链接
采用动态规划的解法,可以借鉴以上题的思路。创建二维dp表,dp[i][j]表示 i 到 j 位置变成回文串的最小操作次数。(我们人为规定 i 是小于等于 j 的)
同样我们要分 s[i] == s[j] 和 s[i] != s[j] 的情况讨论。
如果 s[i] == s[j]
如果 s[i] != s[j]
i 和 j 位置的字符不相同,那么此时只需找到 dp[i+1][j] 和 dp[i][j-1] 的最小值,然后将最小的那个加1即可,即dp[i][j] = min(dp[i+1][j], dp[i][j-1]) + 1; 就相当于再在另一端补上一个与 s[i] 或者 s[j] 相同的字符即可。
这道题和第四题一样,都是需要考虑越界的,具体原因和第四题相同。
在求dp[i][j]的时候,我们可能会用到 i + 1 和 j - 1,在它们有可能越界的时候,一定是 i 等于 j 的时候。我们创建的dp表是二维的,我们可以想到,在可能越界的时候,就是左上角的位置或者右下角的位置,但其实这两个位置满足 i == j,那么dp[i][j] 就会被直接赋值为0,此时就不会用到 i + 1 和 j - 1 了,所以其实我们不用考虑越界的情况。
class Solution {
public:
int minInsertions(string s) {
int n = s.size();
// dp[i][j]表示s[i, j]变成回文串的最小操作次数
vector<vector<int>> dp(n, vector<int>(n, INT_MAX));
// dp[i][j] 需要用到 dp[i+1][j-1]
// 所以i应该从大到小遍历,j应该从小到大遍历
// 且i是要小于等于j的,所以i的初始值为j
// 因为i的初始值为j,所以j在循环外层,i在内层
for (int j = 0; j < n; ++j)
{
for (int i = j; i >= 0; --i)
{
if (s[i] == s[j])
dp[i][j] = i + 1 < j ? dp[i+1][j-1] : 0;
else
dp[i][j] = min(dp[i][j-1], dp[i+1][j]) + 1;
}
}
return dp[0][n-1];
}
};