分割回文串 II
题目链接:
题目
给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是
回文串
。
返回符合要求的 最少分割次数 。
示例 1:
输入:s = "aab" 输出:1 解释:只需一次分割就可将 s 分割成 ["aa","b"] 这样两个回文子串。
示例 2:
输入:s = "a" 输出:0
示例 3:
输入:s = "ab" 输出:1
提示:
1 <= s.length <= 2000
s
仅由小写英文字母组成
解法
算法原理与解析
我们这题使用动态规划,我们做这类题目可以分为以下五个步骤
- 状态显示
- 状态转移方程
- 初始化(防止填表时不越界)
- 填表顺序
- 返回值
- 状态显示
dp[i] 表示 : s 中 [0, i] 区间上的字符串,最少分割的次数。
- 状态转移方程
状态转移⽅程⼀般都是根据「最后⼀个位置」的信息来分析:设 0 <= j <= i ,那么我们可以 根据 j ~ i 位置上的⼦串是否是回⽂串分成下⾯两类:
- 当 [j ,i] 位置上的⼦串能够构成⼀个回⽂串,那么 dp[i] 就等于 [0, j - 1] 区间上最少回⽂串的个数 + 1,即 dp[i] = dp[j - 1] + 1 。
- 当 [j ,i] 位置上的子串不能构成⼀个回⽂串,此时 j 位置就不⽤考虑。
由于我们要的是最⼩值,因此应该循环遍历⼀遍 j 的取值,拿到⾥⾯的最⼩值即可。优化:我们在状态转移⽅程里面分析到,要能够快速判读字符串⾥⾯的⼦串是否回⽂。因此,我们可以先处理⼀个 dp 表,⾥⾯保存所有⼦串是否回⽂的信息
- 初始化(防止填表时不越界)
观察「状态转移⽅程」,我们会用到 j - 1 位置的值。我们可以思考⼀下当 j == 0 的时候,表示的区间就是 [0, i] 。如果 [0, i] 区间上的字符串已经是回⽂串了,最小的回⽂串就是 1 了, j 往后的值就不⽤遍历了。因此,我们可以在循环遍历 j 的值之前处理 j == 0 的情况,然后 j 从 1 开始循环。但是,为了防⽌求 min 操作时, 0 ⼲扰结果。我们先把表⾥⾯的值初始化为「⽆穷⼤」。
- 填表顺序
毫⽆疑问是「从左往右」。
- 返回值
根据「状态表⽰」,应该返回 dp[n - 1] 。
代码实现
class Solution {
public:
int minCut(string s)
{
int n = s.size();
vector<vector<bool> > check(n, vector<bool>(n)); // 表示下标以i和j为两头的字符串是否为回文串
vector<int> dp(n, INT_MAX); // 表示字符串[0,i]中最少的分割次数
// 填表check,从下向上填
for (int i = n - 1; i >= 0; i--)
{
for (int j = i; j < n; j++)
{
if (s[i] == s[j])
{
check[i][j] = i + 1 < j ? check[i + 1][j - 1] : true;
}
}
}
// 填表dp,从左往右
for (int i = 0; i < n; i++)
{
if (check[0][i])
dp[i] = 0;
else
{
for (int j = 1; j <= i; j++)
{
if (check[j][i])
dp[i] = min(dp[i], dp[j - 1] + 1);
}
}
}
return dp[n - 1];
}
};