给你一个字符串
s
,请你将s
分割成一些子串,使每个子串都是回文。返回符合要求的 最少分割次数 。
示例 1:
输入:s = "aab"
输出:1
解释:只需一次分割就可将 s 分割成 ["aa","b"] 这样两个回文子串。
示例 2:
输入:s = "a"
输出:0
动态规划
把字符串分割成多个子串,要求所有子串是回文串,返回最少的分割次数。最直接的做法是,把所有的子串都列举出来,从中找到最少的一种组合。题目 131. 分割回文串 就是找到所有的回文串组合,但是使用这种方法在 LeetCode 提交结果如下,需要使用新的解法:
假设字符串
可以看出,如果
如果字符串$s$有多种分割方法,假设有三种方法,如下所示:
1、$s[0,i_{11}],s[i_{11}+1,i_12],s[i_{12}+1,i_{13}],...,s[i_{k1-1},i_{k1}],s[i_{k1}+1,n-1]$,分割次数
2、$s[0,i_{21}],s[i_{21}+1,i_{22}],s[i_{22}+1,i_{23}],...,s[i_{k2-1},i_{k2}],s[i_{k2}+1,n-1]$,分割次数
3、$s[0,i_{31}],s[i_{31}+1,i_{32}],s[i_{32}+1,i_{33}],...,s[i_{k3-1},i_{k3}],s[i_{k3}+1,n-1]$,分割次数
要找到最少的分割次数,就是在
因此,问题的子问题就为:在$s[0,i_{k1}]、s[0,i_{k2}]、s[0,i_{k3}]$的分割次数中找到最小值,并且$s[0,i_{k1}]、s[0,i_{k2}]、s[0,i_{k3}]$的分割次数也可以通过求解它们的子问题得到。
第二类:对某种数据结构和算法的使用
使用的算法:动态规划
数据结构:构造状态数组
从前面已经知道,在$s[0,n-1]$ 范围内找到最少分割次数,就是要找到所有的回文子串下标$k(k1、k2、k3....)$,$s[0,n-1]$ 的最少分割次数等于$ s[0,k1]、s[0,k2]、s[0,k3]、... $ 中最小分割次数加1。
因此,我们可以构造一个数组,使用动态规划来求解最少分割次数,求解过程如下:
1、要求解的“状态”是:到字符串任意下标
2、简化状态,得到“base case”:$i == 0$ 或者
3、构造“数组”:构造数组
4、构造状态转移方程:
$$
dp[i]=
\begin{cases}
0, & s[0,i]; \text{是回文子串}
\[2ex] min{dp[k]}, & k < i; and; s[k,i];\text{是回文子串}
\end{cases}
$$
从“构造函数”可以看出,在确定
代码实现在“编码阶段”,在 LeetCode 的提交结果如下:
class Solution {
public int minCut(String s) {
int n = s.length();
boolean[][] isPalindrome = isPalindrome(s);
int[] dp = new int[n];
// 初始值
Arrays.fill(dp, Integer.MAX_VALUE);
for (int i = 0; i < n; i++) {
if (isPalindrome[0][i]) {
dp[i] = 0;
} else {
for (int k = 0; k < i; k++) {
if (isPalindrome[k + 1][i]) {
// 依次遍历下标 k,取出最小值
dp[i] = Math.min(dp[i], dp[k] + 1);
}
}
}
}
return dp[n - 1];
}
private boolean[][] isPalindrome(String s) {
int n = s.length();
boolean[][] dp = new boolean[n][n];
for (int i = n - 1; i >= 0; i--) {
for (int j = i; j < n; j++) {
if (i == j) {
dp[i][j] = true;
} else {
boolean b = s.charAt(i) == s.charAt(j);
if (j == i + 1) {
dp[i][j] = b;
} else {
dp[i][j] = b & dp[i + 1][j - 1];
}
}
}
}
return dp;
}
}
“分割回文串II”解题思路:
1、先用一个简单的例子,找到问题的子问题:最少次数是在多个回文子串中取最小值加1,再不断缩小子问题;
2、构造状态数组,直接保存状态:$s[0,i]$ 的最少分割次数。