【LeetCode】分割回文串 [M](深度优先遍历)
2023-09-27 14:19:51 时间
一、题目
给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是 回文串 。返回 s
所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
示例 1:
输入:s = "aab" 输出:[["a","a","b"],["aa","b"]]
示例 2:
输入:s = "a" 输出:[["a"]]
提示:
1 <= s.length <= 16
s
仅由小写英文字母组成
二、代码
class Solution {
public List<List<String>> partition(String s) {
// 双层List,用来记录所有的分割方案
List<List<String>> ans = new ArrayList<>();
// 如果字符串只有一个字符,直接将其加入到答案中
if (s == null || s.length() < 2) {
List<String> cur = new ArrayList<>();
cur.add(s);
ans.add(cur);
} else {
char[] str = s.toCharArray();
int N = str.length;
// check[i][j]:i~j范围的字符串是回文串就为true,不是回文串就是false
boolean[][] checkMap = createCheckMap(str, N);
// 递归回溯,找到所有的分割情况
process(s, 0, 1, checkMap, new ArrayList<>(), ans);
}
return ans;
}
// 构造回文串检查预处理结构
public static boolean[][] createCheckMap(char[] str, int n) {
// check[i][j]:i~j范围的字符串是回文串就为true,不是回文串就是false
boolean[][] check = new boolean[n][n];
// 先给对角线赋值赋值,只有一个字符肯定都是回文串
for (int i = 0; i < n; i++) {
check[i][i] = true;
}
// 再给第二条对角线赋值,两个字符,如果两个字符相等,那么就是回文串,如果两个字符不等,就不是回文串
for (int i = 0; i < n - 1; i++) {
check[i][i + 1] = str[i] == str[i + 1];
}
// 没有i > j的情况,dp数组左下半部分无效
// 给普遍位置赋值
for (int i = n - 3; i >= 0; i--) {
for (int j = i + 2; j < n; j++) {
// 当str[i] == str[j],并且i+1~j-1范围还是一个回文串,就说明i~j范围是一个回文串
check[i][j] = str[i] == str[j] && check[i + 1][j - 1];
}
}
return check;
}
// s[0....i-1]的划分方案都存到path里去了
// 此时对s[i..j-1]进行考察,看s[i...j - 1]是否为一个回文串,如果是的话,就将i~j-1部分分割出来加入到path,然后再去尝试考察s[j...]如何切分
public static void process(String s, int i, int j, boolean[][] checkMap, List<String> path, List<List<String>> ans) {
// s[i...N-1] 已经递归到结尾了
if (j == s.length()) {
// 此时0~i-1的划分方案都已经放入到path了,如果此时i~j-1是一个回文串,并且j遍历完了整个字符串,就说明我们将i~j-1切分出来,加入到ans中,此时ans中就收集好了一种切分方案
if (checkMap[i][j - 1] ) {
// 将i~j-1切分出来加入的path中
path.add(s.substring(i, j));
// 将path加入到ans,至此ans收集到了一种切分方案
ans.add(copyStringList(path));
// 恢复现场,需要将path中的最后一个切分删掉,留给其他的尝试
path.remove(path.size() - 1);
}
// s[i...j-1] 还没有递归到结尾
} else {
// 判断能否回溯的条件,如果此时i~j-1是回文串,所以i~j-1是可以切分出来的,加入到path中
if (checkMap[i][j - 1]) {
// 将i~j-1切分出来加入到path中
path.add(s.substring(i, j));
// 此时0~j-1都已经找到切分方案加入到path了
// 下面我们需要去尝试在j~N-1范围内找到一个可以向下走的分支
// 从判断j~j+1范围开始,看j~j+1是否为一个回文串可以且分出来
process(s, j, j + 1, checkMap, path, ans);
// 恢复现场
path.remove(path.size() - 1);
}
// 执行到这里,0~i-1的切分方法都已经加入到path了,并且切分i~j-1的方案也已经尝试了(i~j-1有可能可以切分,也有可能无法切分,取决于checkMap[i][j - 1])
// 下面我们继续向下递归,到下一个划分点i和j+1,去判断i~j+1范围的字符串是否为回文串,看能不能将这一部分且分出来。
// 这里不用去考虑是不是要在某些时候将i增加来尝试所有的可能性,因为将i加1进行尝试的前提是i前面已经找到了切分方法,而这个已经囊括在了上面代码的process(s, j, j + 1, checkMap, path, ans)中
process(s, i, j + 1, checkMap, path, ans);
}
}
// 将结果复制到新的List中返回
public static List<String> copyStringList(List<String> list) {
List<String> ans = new ArrayList<>();
for (String str : list) {
ans.add(str);
}
return ans;
}
}
三、解题思路
这道题就是写一个暴力尝试,将所有可能的尝试方法都尝试一遍收集答案。
相关文章
- 【LeetCode】公交路线 [H](宽度优先遍历)
- 【LeetCode】最长递增子序列的个数 [M](动态规划)
- 【LeetCode】二叉搜索树中第K小的元素 [M](二叉树遍历)
- 【LeetCode】从前序与中序遍历序列构造二叉树 [M](二叉树重构)
- 【LeetCode】电话号码的字母组合 [M](深度优先遍历)
- 【LeetCode】前序遍历构造二叉搜索树 [M](单调栈)
- LeetCode_多叉树_中等_429.N 叉树的层序遍历
- LeetCode_二分搜索_双指针_中等_287.寻找重复数
- LeetCode_拓扑排序_BFS_中等_210.课程表 II
- LeetCode_二叉搜索树_中等_98.验证二叉搜索树
- LeetCode_多叉树_简单_589.N 叉树的前序遍历
- LeetCode_回溯_中等_78.子集
- LeetCode刷题(12)【简单】最长公共前缀(C++)
- LeetCode·每日一题·1235.规划兼职工作·动态规划
- LeetCode·105.从前序与后序遍历序列构造二叉树·递归
- LeetCode·429.N叉树的层次遍历·层次遍历
- LeetCode·二叉树前序、中序、后序遍历·递归
- LeetCode·134.加油站·贪心
- LeetCode·每日一题·1161.最大层内元素和·层次遍历
- LeetCode·581.最短无序连续子数组·双指针
- [LeetCode] 843. Guess the Word 猜单词
- [LeetCode] 314. Binary Tree Vertical Order Traversal 二叉树的垂直遍历
- [LeetCode] 242. Valid Anagram 验证变位词
- [LeetCode] 112. Path Sum 路径和
- [LeetCode] 94. Binary Tree Inorder Traversal 二叉树的中序遍历
- leetcode 925 长按键入
- leetcode 279 完全平方数
- leetcode 106 从中序和后续遍历序列构造二叉树
- leetcode 144 145 94二叉树的三种非递归遍历