LeetCode 131 132 分割回文串
131. 分割回文串
难度中等648
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
示例 1:
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
示例 2:
输入:s = "a"
输出:[["a"]]
提示:
- 1 <= s.length <= 16
- s 仅由小写英文字母组成
通过次数94,209
提交次数129,420
来自 <https://leetcode-cn.com/problems/palindrome-partitioning/>
思路
- 很显然, 要想找到所有的分割方案, 最起码要知道哪些位置可以分割.因此, 联想到声明一个dp数组用于提前处理并存储i~j之间的字符串是否为回文串.
- 第1点的关键点在于dp数组的建立. 可以发现: dp[i][j] == true的充分必要条件是 dp[i + 1][j - 1] == true && str[i] == str[j]. 填充dp数组无非就是将(1 + N) / 2 * N个空进行装填. 因此, 可以通过双重循环的方式完成.
- 在dp数组建立之后, 就要通过dp数组暴力枚举出所有的答案. 大多这种"分割 "类题目都可以通过回溯法进行暴力枚举.
- 一个极为清晰易懂的题解
我的题解
class Solution {
// i~j之间的字符是否为回文串
boolean[][] is_parlindromic;
String str;
int N;
LinkedList<List<String>> res = new LinkedList<>();
public List<List<String>> partition(String s) {
this.N = s.length();
str = s;
is_parlindromic = new boolean[N][N];
for (int i = N - 1; i >= 0; i--)
{
for (int j = i; j < N; j++)
{
if (j - i + 1 == 1)
{
is_parlindromic[i][j] = true;
} else if (j - i + 1 == 2){
if (str.charAt(i) == str.charAt(j))
is_parlindromic[i][j] = true;
} else {
is_parlindromic[i][j] =
is_parlindromic[i + 1][j - 1] && (str.charAt(i) == str.charAt(j));
}
}
}
search(0, N - 1, new LinkedList<String>());
return res;
}
public void search(int l, int r, List<String> cur){
if (l == r + 1)
{
res.add(new LinkedList<String>(cur));
return;
}
for (int i = l; i <= r; i++)
{
if(is_parlindromic[l][i])
{
cur.add(str.substring(l, i + 1));
search(i + 1, r, cur);
cur.remove(cur.size() - 1);
}
}
}
}
132. 分割回文串 II
难度困难398
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文。
返回符合要求的 最少分割次数 。
示例 1:
输入:s = "aab"
输出:1
解释:只需一次分割就可将 s 分割成 ["aa","b"] 这样两个回文子串。
示例 2:
输入:s = "a"
输出:0
示例 3:
输入:s = "ab"
输出:1
提示:
- 1 <= s.length <= 2000
- s 仅由小写英文字母组成
通过次数41,090
提交次数83,866
来自 <https://leetcode-cn.com/problems/palindrome-partitioning-ii/>
思路
- 对于一个str, 其对应is_palindrome[i][j]的算法与"分割回文串I"中相同
- 对比第I题, 题II不求所有的分割情况, 而是求最少的分割次数
- 看到"最小"这个字眼, 就应该立刻想到是否能用贪心算法或动态规划算法进行解决
- 经过思考, 明显贪心算法是行不通的. 但却找到了这道题的最小解结构, 这说明这道题就要用动态规划解决
- 如何动态规划 :
- 设dp[i] = a表示: str从0到i之间的子串的最小分割次数为a
- 假设dp[0]...dp[i]已知,
- 若0~i之间的子串都为回文串, 即is_palindrome[0][i]=true, 那么dp[i] = 0
- 若is_palindrome[0][i]=false. 那么dp[i+1]=MIN(dp[j1], dp[j2], dp[j3], ..., dp[jn]) + 1
- 求出dp数组后, dp[N - 1]就是最终的解
题解
class Solution {
int[] dp;
boolean[][] is_palindrome;
int N;
public int minCut(String s) {
this.N = s.length();
this.is_palindrome = new boolean[N][N];
this.dp = new int[N];
for (int i = 0; i < N; i++)
{
for (int j = i; j >= 0; j--)
{
if (i - j + 1 == 1)
{
is_palindrome[j][i] = true;
}
else if (i - j + 1 == 2)
{
is_palindrome[j][i] = (s.charAt(i) == s.charAt(j));
}
else {
is_palindrome[j][i] = (s.charAt(i) == s.charAt(j)) && is_palindrome[j + 1][i - 1];
}
}
}
// dp的初始条件
dp[0] = 0;
for (int i = 1; i < N; i++)
{
dp[i] = dp[i - 1] + 1;
for (int j = 0; j <= i; j++)
{
if (is_palindrome[j][i]){
if(j == 0){
dp[i] = 0;
break;
}
dp[i] = Math.min(dp[i], dp[j - 1] + 1);
}
}
}
return dp[N - 1];
}
}