一、题目描述
给你一个字符串 s,请你将 **s **分割成一些 子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
示例 1:
输入: s = "aab"
输出: [["a","a","b"],["aa","b"]]
示例 2:
输入: s = "a"
输出: [["a"]]
提示:
1 <= s.length <= 16s仅由小写英文字母组成
二、解答
题目分析
-
问题描述:
- 给定一个字符串
s,需要将其分割成若干子串,且每个子串都要是回文串(即正读和反读都一样的字符串)。 - 最终要返回所有可能的符合条件的分割方案。每个分割方案表示为一个字符串数组,数组中的每个元素是一个回文子串。
- 给定一个字符串
-
输入输出要求:
- 输入是一个字符串
s,其长度范围是1 <= s.length <= 16,并且s仅由小写英文字母组成。 - 输出是一个二维字符串数组,其中每一个子数组表示一种可能的分割方案,子数组中的元素是回文子串。
- 输入是一个字符串
-
问题难点:
- 如何确定所有可能的分割方式,因为分割点的选择有多种可能性。
- 对于每一种分割方式,需要判断分割出的子串是否为回文串。
- 要收集所有符合条件的分割方案,不能遗漏任何一种可能的情况。
示例分析
-
示例 1:
-
输入
s = "aab"。 -
对于字符串
"aab",存在两种合法的分割方式:- 第一种分割方式是将其分割成
["a","a","b"],其中"a"是回文串,"a"是回文串,"b"也是回文串,满足每个子串都是回文串的要求。 - 第二种分割方式是将其分割成
["aa","b"],"aa"是回文串,"b"是回文串,也满足条件。
- 第一种分割方式是将其分割成
-
所以输出
[["a","a","b"],["aa","b"]]。
-
-
示例 2:
- 输入
s = "a"。 - 对于字符串
"a",只有一种分割方式,即将其看作一个整体"a",而"a"本身就是一个回文串。 - 所以输出
[["a"]]。
- 输入
代码
class Solution {
boolean[][] 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 boolean[n][n];
for (int i = 0; i < n; ++i) {
Arrays.fill(f[i], true);
}
for (int i = n - 1; i >= 0; --i) {
for (int j = i + 1; j < n; ++j) {
f[i][j] = (s.charAt(i) == s.charAt(j)) && f[i + 1][j - 1];
}
}
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 (f[i][j]) {
ans.add(s.substring(i, j + 1));
dfs(s, j + 1);
ans.remove(ans.size() - 1);
}
}
}
}
代码分析
class Solution {
// f 是一个二维布尔数组,用于记录字符串 s 中从索引 i 到索引 j 的子串是否为回文串
boolean[][] f;
// ret 用于存储所有可能的分割方案,每个方案是一个字符串列表
List<List<String>> ret = new ArrayList<List<String>>();
// ans 用于临时存储当前正在构建的分割方案
List<String> ans = new ArrayList<String>();
// n 表示字符串 s 的长度
int n;
public List<List<String>> partition(String s) {
// 获取字符串 s 的长度
n = s.length();
// 初始化二维布尔数组 f,用于记录子串是否为回文串
f = new boolean[n][n];
// 先将 f 数组的所有元素初始化为 true,即默认所有子串都是回文串
for (int i = 0; i < n; ++i) {
Arrays.fill(f[i], true);
}
// 动态规划计算所有子串是否为回文串
// 从后往前遍历,这样可以利用之前计算的结果
for (int i = n - 1; i >= 0; --i) {
// j 从 i+1 开始,因为当 i == j 时,单个字符一定是回文串,已经初始化为 true
for (int j = i + 1; j < n; ++j) {
// 判断从 i 到 j 的子串是否为回文串
// 条件是当前字符 s.charAt(i) 等于 s.charAt(j),并且去掉首尾字符后的子串 f[i + 1][j - 1] 也是回文串
f[i][j] = (s.charAt(i) == s.charAt(j)) && f[i + 1][j - 1];
}
}
// 从索引 0 开始进行深度优先搜索
dfs(s, 0);
// 返回所有可能的分割方案
return ret;
}
public void dfs(String s, int i) {
// 当 i 等于 n 时,说明已经遍历完整个字符串,找到了一种有效的分割方案
if (i == n) {
// 将当前的分割方案 ans 复制一份添加到 ret 中
ret.add(new ArrayList<String>(ans));
return;
}
// 从索引 i 开始,尝试所有可能的分割点 j
for (int j = i; j < n; ++j) {
// 如果从 i 到 j 的子串是回文串
if (f[i][j]) {
// 将该回文子串添加到当前的分割方案 ans 中
ans.add(s.substring(i, j + 1));
// 递归调用 dfs 函数,从 j + 1 开始继续分割
dfs(s, j + 1);
// 回溯操作,移除最后添加的子串,尝试其他可能的分割方案
ans.remove(ans.size() - 1);
}
}
}
}
类与成员变量
class Solution {
boolean[][] f;
List<List<String>> ret = new ArrayList<List<String>>();
List<String> ans = new ArrayList<String>();
int n;
}
f:这是一个二维布尔数组,f[i][j]用于标记字符串从索引i到索引j的子串是否为回文串。ret:这是一个存储最终结果的二维列表,每个子列表代表一种可能的分割方案,其中每个元素是一个回文子串。ans:这是一个临时列表,用于在深度优先搜索过程中构建当前的分割方案。n:表示输入字符串的长度。
partition 方法
public List<List<String>> partition(String s) {
n = s.length();
f = new boolean[n][n];
for (int i = 0; i < n; ++i) {
Arrays.fill(f[i], true);
}
for (int i = n - 1; i >= 0; --i) {
for (int j = i + 1; j < n; ++j) {
f[i][j] = (s.charAt(i) == s.charAt(j)) && f[i + 1][j - 1];
}
}
dfs(s, 0);
return ret;
}
-
初始化:
- 首先获取输入字符串
s的长度n。 - 接着创建并初始化二维布尔数组
f,将所有元素初始化为true,这意味着默认所有子串都是回文串。
- 首先获取输入字符串
-
动态规划计算回文子串:
- 采用两层嵌套循环来填充
f数组。外层循环从字符串末尾开始向前遍历,内层循环从当前外层循环索引的下一个位置开始往后遍历。 f[i][j]的值由两个条件决定:当前位置i和j的字符是否相等,以及去掉首尾字符后的子串f[i + 1][j - 1]是否为回文串。
- 采用两层嵌套循环来填充
-
深度优先搜索:
- 调用
dfs方法,从字符串的起始位置(索引为 0)开始进行深度优先搜索。 - 最后返回存储所有分割方案的
ret列表。
- 调用
dfs 方法
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 (f[i][j]) {
ans.add(s.substring(i, j + 1));
dfs(s, j + 1);
ans.remove(ans.size() - 1);
}
}
}
-
递归终止条件:
- 当
i等于n时,表明已经遍历完整个字符串,此时将当前的分割方案ans复制一份添加到ret列表中,并终止当前递归调用。
- 当
-
尝试所有可能的分割点:
- 使用
for循环从当前索引i开始,尝试所有可能的分割点j。 - 若
f[i][j]为true,说明从索引i到j的子串是回文串,将该子串添加到ans列表中。 - 递归调用
dfs方法,从j + 1开始继续分割字符串。 - 进行回溯操作,移除
ans列表中最后添加的子串,以便尝试其他可能的分割方案。
- 使用
代码总结
代码使用了动态规划和深度优先搜索(DFS)的方法来解决将字符串分割成回文子串的问题。首先通过动态规划预处理出所有子串是否为回文串,然后使用 DFS 递归地尝试所有可能的分割方案,并将符合条件的方案添加到结果列表中。