132. 分割回文串 II
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文。
返回符合要求的 最少分割次数 。
示例 1:
输入:s = “aab” 输出:1 解释:只需一次分割就可将 s 分割成 [“aa”,“b”] 这样两个回文子串。
示例 2:
输入:s = “a”
输出:0
示例 3:
输入:s = “ab”
输出:1
提示:
1 <= s.length <= 2000
s 仅由小写英文字母组成
解析
- 字符串的问题通常会有这样一种想法?在前一个状态的基础上新添加一个字符之后会有什么影响。这样也就可以使用动态规划的做法了。
- 这个问题还有一个特点就是连续子串,连续区间问题,要求字符串是连续。那新添加一个字符之后,增添的情况无非就是新加的这个字符和前边所有字符结合之后对结果的影响。
例如:s=“acdcdb”, 用f[i]表示以s[i]结尾的字符串需要分割次数。
1,f[i]=min( f[i] , f[j]+1 ) 其中,s[j+1->i]为回文
2,a的基础上添加c,ac不是回文,c与a结合之后需要一次分割f[1]=1
3,ac的基础上添加d,cd不是回文,acd也不是回文因此f[2]=2
4, acd的基础上添加c,acdc不是回文,cdc是回文,dc不是回文。
因此, f[3]=f[1]+1=2;
5, 在此问题中可以发现,每次枚举j (0=<j<i)需要判断s.substr(j+1,i-j+1)是否为回文串,一次还需要一次动态规划判断是否是回文串。
6, i开头,j结尾 的字符串是否是回文串用g[i][j]表示,可以发现状态转移方程:g[i][j]=(s[i]==s[j]&&g[i+1][j-1])
code
class Solution {
public:
int minCut(string s) {
int n = s.size();
vector<vector<int>> g(n, vector<int>(n, true));
// i的状态要依赖于i+1,因此i从大到小填充dp数组
// j依赖于j-1因此需要从小到达填充dp数组
for (int i = n - 1; i >= 0; --i) {
for (int j = i + 1; j < n; ++j) {
g[i][j] = (s[i] == s[j]) && g[i + 1][j - 1];
}
}
vector<int> f(n, INT_MAX);
for (int i = 0; i < n; ++i) {
// s[0-i]整体是一个回文串,
//开头为0需要单独判断,看一下f转移就可以发现
// 如果0-i是回文,那么应该用f[-1]+1,因此要单独判断,或者特殊处理
if (g[0][i]) {
f[i] = 0;
}
else {
// 以i为结尾,枚举开始位置
// (j+1到i如果为回文,那么需要切割长度就是 f[j]+1)
for (int j = 0; j < i; ++j) {
if (g[j + 1][i]) {
f[i] = min(f[i], f[j] + 1);
}
}
}
}
return f[n - 1];
}
};