Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
前言
力扣第132题 分割回文串 II 如下所示:
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文。
返回符合要求的 最少分割次数 。
示例 1:
输入: s = "aab"
输出: 1
解释: 只需一次分割就可将 s 分割成 ["aa","b"] 这样两个回文子串。
示例 2:
输入: s = "a"
输出: 0
一、思路
这题与前面一题力扣第131题-分割回文串非常类似,不过这一题只需要获得最小的分割次数。
两次错误的尝试
下面的思路都有一个前提:通过
动态规划初始化任意两个位置间的子串是否为回文串
我刚开始想的是:既然要获取最小的分割次数,那我每次递归从字符串的最后开始,以保证每次分割的子串尽可能的长,从而让分割的次数最小。伪代码如下所示:
private void dfs(int i, int len, int count){
if (i >= len){
ret = Math.min(ret, count);
return;
}
// 从后向前遍历,找到下一个最长的回文串
for (int j=len-1; j>=i; j--){
if (flag[i][j]){
dfs(j+1, len, count+1);
break;
}
}
}
后面我就提交代码了,发现测试用例 s = aaabaa 是无法通过的。因为 第一次从 0 开始取最长的回文子串,只能取到 aaa,就导致了后续只剩下了 baa,故最小的分割次数为 3。
既然上面这种思路不行,那我就把 break 的逻辑去除了,以保证能够获取到最小的分割次数。事实证明我还是太年轻了。提交后又发现无法通过 s3 = "ababababababababababababcbabababababababababababa" 的测试用力,因为递归会导致时间超时。
动态规划
想要优化递归,不妨尝试一下 动态规划。我们假设 dp[n] 为 0 ~ n 的最小分割次数。状态转移方程为:
- 当
0 ~ n为回文字符串,则dp[n] == 0 - 如
0 ~ n不为回文字符串,则取所有满足i ~ n为回文串(0<i<n)时,dp[n] = min(dp[i] + 1)。翻译一下就是:枚举当前最后一个回文子串,来确定最小分割次数
有一点在实现过程中需要特别注意:当
j ~ i为回文串的时候,dp[i] = Math.min(dp[i], dp[j-1] + 1),其中j - 1表示上一个位置的最小分割次数
二、实现
实现代码
实现代码与思路中保持一致
public int minCut(String s) {
int len = s.length();
boolean[][] flag = new boolean[len][len];
for (int i = len - 1; i >= 0; i--)
{
for (int j = i; j < len; ++j)
{
if (i == j) // 对角线
flag[i][j] = true;
else if (j == i + 1)
flag[i][j] = s.charAt(i) == s.charAt(j);
else
flag[i][j] = s.charAt(i) == s.charAt(j) && flag[i + 1][j - 1];//s[i]是否等于s[j] && 小区间是否为回文串
}
}
int[] dp = new int[len];
for (int i = 0; i<len; i++){
if (flag[0][i]){
dp[i] = 0;
} else {
dp[i] = len;
// 枚举
for (int j=1; j<=i; j++){
if (flag[j][i]){
dp[i] = Math.min(dp[i], dp[j-1] + 1);
}
}
}
}
return dp[len-1];
}
测试代码
public static void main(String[] args) {
String s = "aab";
String s1 = "ab";
String s2 = "aaabaa";
String s3 = "ababababababababababababcbabababababababababababa";
int ret = new Number132().minCut(s);
System.out.println(ret);
}
结果
三、总结
感谢看到最后,非常荣幸能够帮助到你~♥
如果你觉得我写的还不错的话,不妨给我点个赞吧!如有疑问,也可评论区见~