647. 回文子串 - 力扣(LeetCode)
class Solution
{
public:
int countSubstrings(string s)
{
int n = s.size();
vector<vector<bool>> dp(n, vector<bool>(n, false));
int ret = 0;
// 最开始填的是dp[n-1][n-1]
for (int i = n - 1; i >= 0; i--)
{
for (int j = n - 1; j >= i; j--)
{
if (s[i] == s[j])
{
if (i == j || i + 1 == j)
dp[i][j] = true;
else
{
dp[i][j] = dp[i + 1][j - 1];
}
}
if (dp[i][j])
ret++;
}
}
return ret;
}
};
// 可以用n^2级别的时间复杂度将所有子串是否是回文串保留到dp表中 这只是一个引子 虽然在本题效率不高 但是有些题想到了可以轻松将hard转为easy
// dp[i][j]:i、j维护子串的开始位置和结束位置 i、j分别像后面遍历 i外层 j遍历到n-1 之后i++ j只需要从i位置开始即可 因为j从头开始[j, i] 子串 而这个子串一定有一次[i, j]统计了 j=i开始遍历避免重复
// dp中的值为true或者false
// dp表中只有右上角的部分包括正对角线用到 因为i一定小于等于j
// 状态转移方程
// 在s[i] == s[j]的情况下若是i==j 那么一定是true i+1==j一定是true i、j之间存在其他元素 那么就需要看dp[i+1][j-1]是不是true即可 是则dp[i][j] = true 反之false
// 初始化 全部为false
// 就算用到i+1 j-1也不影响 因为这种情况一定不会越界 这种情况表明ij之间一定有其他元素
// 填表顺序
// 需要用到左下角的dp 那么从下往上填表即可 保证上面填表时用到下面dp时一定有合法值
// 返回值 返回dp表中true的个数即可 因为dp[i][j]表示ij子串是不是会问换 而ij遍历了s中的所有子串
5. 最长回文子串 - 力扣(LeetCode)
class Solution
{
public:
string longestPalindrome(string s)
{
int n = s.size();
vector<vector<bool>> dp(n, vector<bool>(n, false));
int begin = 0, end = 0, maxlen = 0;
for (int i = n - 1; i >= 0; i--)
{
for (int j = i; j < n; j++)
{
if (s[i] == s[j])
{
if (i == j || i + 1 == j)
{
dp[i][j] = true;
}
else
{
dp[i][j] = dp[i + 1][j - 1];
}
}
if (dp[i][j])
{
if (j - i + 1 > maxlen)
{
maxlen = j - i + 1;
begin = i;
end = j;
}
}
}
}
return s.substr(begin, end - begin + 1);
}
};
// 和回文串个数那题一样 只不过需要记录最长的回文串的起始位置和结束位置即可
1745. 分割回文串 IV - 力扣(LeetCode)
class Solution
{
public:
bool checkPartitioning(string s)
{
int n = s.length();
vector<vector<bool>> dp(n, vector<bool>(n, false));
for (int i = n - 1; i >= 0; i--)
{
for (int j = i; j < n; j++)
{
if (s[i] == s[j])
dp[i][j] = i == j || i + 1 == j ? true : dp[i + 1][j - 1];
}
}
// 枚举分割点
for (int point1 = 0; point1 < n - 1; point1++)
{
if (!dp[0][point1])
continue;
for (int point2 = point1 + 2; point2 < n; point2++)
{
if (dp[point1 + 1][point2 - 1] && dp[point2][n - 1])
{
return true;
}
}
}
return false;
}
};
// 先使用找所有子串中的回文子串dp思想做一个预处理 找出所有回文子串
// 之后在这个二维dp表中找出可以拼接为完整字符串的三个回文子串 找得到返回true否则false
// 结果的三个回文子串之间的关系 首子串坐标必须是(0, j) 尾子串必须是(i, n-1) 遍历所有这些子串 找到一个坐标为(j+1, i-1)的中间子串并且三个长度加起来为n则表示可以分割 具体枚举第二个字符串的起始结束位置即可
132. 分割回文串 II - 力扣(LeetCode)
class Solution
{
public:
int minCut(string s)
{
int n = s.length();
vector<vector<bool>> vis(n, vector<bool>(n, false)); // 记录ij之间子串是否是回文串 -- 预处理
for (int i = n - 1; i >= 0; i--)
{
for (int j = i; j < n; j++)
{
if (s[i] == s[j])
vis[i][j] = i == j || i + 1 == j ? true : vis[i + 1][j - 1];
}
}
vector<int> dp(n, INT_MAX);
for (int i = 0; i < n; i++)
{
if (vis[0][i])
dp[i] = 0;
else
{
for (int j = 1; j <= i; j++)
{
if (vis[j][i])
dp[i] = min(1 + dp[j - 1], dp[i]);
}
}
}
return dp[n - 1];
}
};
// 状态表示
// 定义dp[i]:0~i子串中分割为若干回文串的最少分割次数
// 状态转移方程
// 若是0~i子串本身就是回文串 那么dp[i] = 0
// 否则 假设在0~i之间切一下 j位置为分割点 若是j~i子串为回文串 那么dp[i] = 1 + dp[j-1]即可(0<j<=i) 为0则就是0~i子串 而已经知道不是回文串 所以这个点不需要遍历 遍历过程中取min(1+dp[j])即可
// 因为要频繁用到两个位置之间是否是回文串 可以预处理 用一个二维数组记录位置之间是否是回文串
// 初始化 使用到j-1 但是j不可能为0 所以没影响 但是dp[i] = min(dp[i], 1+dp[j-1])取最小值的原因 dp不能初始化为0 否则dp[i]全为0了 全部初始化为最大值
// 返回值
// 返回dp[n-1]即可
516. 最长回文子序列 - 力扣(LeetCode)
class Solution
{
public:
int longestPalindromeSubseq(string s)
{
int n = s.size();
vector<vector<int>> dp(n, vector<int>(n, 1));
int ret = 1;
for (int i = n - 1; i >= 0; i--)
{
for (int j = i; j < n; j++)
{
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]);
ret = max(ret, dp[i][j]);
}
}
return ret;
}
};
// 状态表示:
// dp[i][j] 表示以ij位置为边界的子串中所有回文子串中的最长长度
// 状态转移方程
// 若是s[i] == s[j] : i == j时 dp[i][j] = 1 ; i+1 == j 时 dp[i][j] = 2 ; 否则dp[i][j] = 2 + dp[i+1][j-1]
// 若是s[i] != s[j] : dp[i][j] = max(dp[i+1][j], dp[i][j-1]);
// 初始化
// 的时候需要用到当前位置的左边和下面元素 因此只需要特殊处理左上角和右下角的元素 但是这两个位置s[i] == s[j] 进不去s[i] != s[j]的判断 因此不会越界
// 填表顺序
// 大方向从下往上 之后从左往右
// 返回值
// 返回dp表中最大的长度
1312. 让字符串成为回文串的最少插入次数 - 力扣(LeetCode)
class Solution
{
public:
int minInsertions(string s)
{
int n = s.size();
vector<vector<int>> dp(n, vector<int>(n));
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] : 0;
}
else
{
dp[i][j] = 1 + min(dp[i+1][j], dp[i][j-1]);
}
}
}
return dp[0][n-1];
}
};
// 状态表示:
// dp[i][j] 表示让以i、j位置元素为边界的子串成为回文串的最少插入次数
// 状态转移方程:
// s[i] == s[j] : i == j || i + 1 == j dp[i][j] = 0 ; i + 1 < j dp[i][j] = dp[i+1][j-1]
// s[i] != s[j] : 首先得让边界先相同 可以在i左边加一个s[j] 或者在j右边加一个s[i] 对应的操作次数为 1 + dp[i][j-1] 1 + dp[i+1][j]
// 初始化
// 全部初始化为0即可 画表带入状态转移方程都不会越界
// 填表顺序
// 从下往上 从左往右
// 返回值
// 返回dp[0][n-1]
112



被折叠的 条评论
为什么被折叠?



