「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。
感觉2022来的挺快的,2021有很多想做的事情还没完成。算法也学的断断续续,希望2022可以把上一年未完成的任务都补上。
题目
给你一个字符串
s,请你将 **s**分割成一些子串,使每个子串都是 回文串 。返回s所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
示例 1:
输入: s = "aab"
输出: [["a","a","b"],["aa","b"]]
示例 2:
输入: s = "a"
输出: [["a"]]
思路
因为要找到所有分割方案,所以这里大方向上应该是回溯。从下标0开始遍历,找到回文串后(假设当前回文串字符开始和下标分别为i、j)加入列表,然后继续从j+1往后遍历和回溯。因为单子字符一定是回文,所以不用担心某一种切割后,后面的字符串无法切换成n个回文串的情况,因为至少可以有一种方案是,把后面的字符串都切成单个字符,一定是一种满足条件的解。
这样,问题就转换成了,如何快速判断s[i][j]是否是一个回文串。
类似的问题应该见过很多了,常见的解法就是打表,把重复计算的中间结果缓存起来,用空间换时间,也是动态规划算法比较重要的思想吧。
我们定义一个布尔类型的2维数组f[i][j],表示s[i][j]是否是一个回文串。因为j一定大于等于i,所以我们只要给一半赋值就行,另外一半是没有意义的。我们可以先把f[0][0]、f[1][1]...f[len-1][len-1]这条对角线值初始化成true,因为单字符一定是回文串,接下来我们分2种情况
- j = i+1 : f[i][j] = s.charAt(i) == s.charAt(j)
- j > i+1 : f[i][j] = s.charAt(i) == s.charAt(j) && f[i+1][j-1] 可以看到s.charAt(i) == s.charAt(j)这一步判断是公共的,我们可以提取出来。另外一边,由于f[i][j]依赖于f[i+1][j-1],所以,我们在给f[i][j]赋值是,i这层的循环到从大到小,这样保证在计算f[i][j]的时候,f[i+1][j-1]已经有值了。
Java版本代码
class Solution {
public List<List<String>> partition(String s) {
int len = s.length();
boolean[][] f = new boolean[len][len];
for (int i = 0; i < len; i++) {
f[i][i] = true;
}
for (int i = len-1; i >= 0; i--) {
for (int j = i+1; j < len; j++) {
f[i][j] = s.charAt(i) == s.charAt(j);
if (f[i][j] && j > i+1) {
f[i][j] = f[i+1][j-1];
}
}
}
List<List<String>> ans = new ArrayList<>();
List<String> temp = new ArrayList<>();
dfs131(f, s, len, 0, ans, temp);
return ans;
}
private static void dfs131(boolean[][] f, String s, int len, int index, List<List<String>> ans, List<String> temp) {
if (index == len) {
ans.add(new ArrayList<>(temp));
return;
}
for (int i = index; i < len; i++) {
if (f[index][i]) {
temp.add(s.substring(index, i + 1));
dfs131(f, s, len, i + 1, ans, temp);
temp.remove(temp.size() - 1);
}
}
}
}