题目
给你一个字符串 s,请你将 s 分割成一些 子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
数据范围
1 <= s.length <= 16
s 仅由小写英文字母组成
测试用例
示例1
java
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
示例2
java
输入:s = "a"
输出:[["a"]]
题解1(官解1,时间On 2n,空间On2)
java
class Solution {
// res 用于存储最终所有可能的分割方案(结果列表)
List<List<String>> res = new ArrayList<>();
// curr 用于存储当前回溯路径中已经切分出来的回文子串
List<String> curr = new ArrayList<>();
// isPd[i][j] 表示字符串从索引 i 到 j 的子串是否为回文串
boolean isPd[][];
int n;
public List<List<String>> partition(String s) {
n = s.length();
// 初始化 DP 表,n+1 是为了防止 i+1 或 j-1 越界时的溢出
isPd = new boolean[n + 1][n + 1];
// 预处理第一步:填充默认值
// 将所有位置设为 true,是因为当子串长度为 1 (i==j) 或长度为 0 (i>j) 时,它们逻辑上是回文的基础
for (int i = 0; i < n; i++) {
Arrays.fill(isPd[i], true);
}
// 预处理第二步:动态规划填表
// 状态转移方程:s[i...j] 是回文 <=> (s[i] == s[j]) 且 (内部子串 s[i+1...j-1] 也是回文)
// 注意:i 需要从大到小遍历,因为计算 isPd[i] 需要用到 i+1 的结果
for (int i = n - 1; i >= 0; i--) {
for (int j = i + 1; j < n; j++) {
isPd[i][j] = (s.charAt(i) == s.charAt(j)) && isPd[i + 1][j - 1];
}
}
// 从索引 0 开始进行深度优先搜索(回溯)
dfs(s, 0);
return res;
}
/**
* 回溯函数:尝试从 pos 位置开始切分字符串
*/
public void dfs(String s, int pos) {
// 递归出口:如果起始位置已经到达字符串末尾,说明找到了一组完整的分割方案
if (pos == n) {
// 注意:必须 new 一个新列表存入 res,否则 curr 的变化会影响已存入的结果
res.add(new ArrayList<String>(curr));
return;
}
// 从当前位置 pos 开始,尝试所有可能的切分点 j
for (int j = pos; j < n; j++) {
// 利用预处理好的 DP 表,在 O(1) 时间内判断当前切下的子串 s[pos...j] 是否回文
if (isPd[pos][j]) {
// 1. 做选择:将当前回文子串加入路径
curr.add(s.substring(pos, j + 1));
// 2. 递归:从 j+1 的位置继续向后切分
dfs(s, j + 1);
// 3. 撤销选择:回溯,移除最后一个切片,尝试下一种切分可能
curr.remove(curr.size() - 1);
}
}
}
}
题解2(官解2,时空同上)
java
class Solution {
int[][] f;
List<List<String>> ret = new ArrayList<List<String>>();
List<String> ans = new ArrayList<String>();
int n;
public List<List<String>> partition(String s) {
n = s.length();
f = new int[n][n];
dfs(s, 0);
return ret;
}
public void dfs(String s, int i) {
if (i == n) {
ret.add(new ArrayList<String>(ans));
return;
}
for (int j = i; j < n; ++j) {
if (isPalindrome(s, i, j) == 1) {
ans.add(s.substring(i, j + 1));
dfs(s, j + 1);
ans.remove(ans.size() - 1);
}
}
}
// 记忆化搜索中,f[i][j] = 0 表示未搜索,1 表示是回文串,-1 表示不是回文串
public int isPalindrome(String s, int i, int j) {
if (f[i][j] != 0) {
return f[i][j];
}
if (i >= j) {
f[i][j] = 1;
} else if (s.charAt(i) == s.charAt(j)) {
f[i][j] = isPalindrome(s, i + 1, j - 1);
} else {
f[i][j] = -1;
}
return f[i][j];
}
}
思路
这道题稍微比前几道题难一些,主要是考了一些回文的知识,并且考察了如何处理重复计算的方法,本文的题解一是通过动态规划提前预处理,题解二是通过记忆数组,两个方法都可以,动态规划代码简单一些,看个人吧,回溯思想都老生常谈了,就不重复讲解了。