- 原题连接:131. 分割回文串
题目
给你一个字符串 s,请你将_ s _分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
示例 1:
输入:s = “aab”
输出:[[“a”,“a”,“b”],[“aa”,“b”]]
示例 2:
输入:s = “a”
输出:[[“a”]]
提示:
- 1 <= s.length <= 16
- s 仅由小写英文字母组成
1- 思路
回溯三部曲
回溯法分割回文串
- 回溯:for循环遍历的终止条件则为 输入的 String 的长度
- 切割回文串的过程,可以看做是 最初的组合问题,即 借助 startIndex 选取每次选择的数
- 这里的 startIndex 选取的不是数,而是每次选取分割点
理解回溯树,每次在 一个 字符串中选取分割点 初始字符串为 str=“aabc”
- Path1
- 则第一步 选取分割点为 “a|abc”,则此时 结果 a 是回文的可以继续深入
- 第二步 选取分割点为 “a|a|bc” 此时从最后一个分割线往左,有两个分割结果
a|
、a|
都是回文的 - 第三步 分割 “a|a|b|c” 此时分割的结果也是回文的
- 同理分割到最后一位 “a|a|b|c|” 四个结果都是回文的
a|
、a|
、b|
、c|
- Path2
- 回溯到 上述 Path的 第二步 “a|a|bc” 此时切割 整个 “a|a|bc|” 由于
bc|
不回文,则直接返回,回退结点 - 回退到 上述 Path的 第一步 “a|abc”
- “a|ab|c” ——> 不满足回退
- “a|abc|” ——> 不满足回退
- 回到根节点 从 “aa|bc” 开始切割以此类推
具体回溯过程分析:
分析上述回溯步骤后可以知道 回溯树的 数深由回溯控制,而树的宽由 for 循环控制
- 则回溯中的 for 循环的条件则为 遍历 字符串的长度(由于从根结点出发的第二个子树,起始点为 2,则需要借助 startIndex 层去重)
- 1. 去重同层节点的起始位置:通过在每一层循环的时候,从startIndex开始,确保了在同一层中不会重复选择同一个起始位置的字符,从而避免了生成重复的路径。这是因为每一次递归调用backTracing时,startIndex都会更新为当前子串的下一个位置,确保了在这一层的循环中不会再次选择之前已经考虑过的字符作为起点。
- 2. 去重每条路径深入往下可选择的剩余节点的起始位置:当递归调用backTracing(s, i + 1)时,startIndex更新为i + 1,这意味着在当前路径深入探索时,只会考虑当前选择的子串之后的字符,从而避免了在路径中重复选择相同的字符序列。这样的设计保证了每次递归深入时,都是基于当前路径的新的起始位置,避免了生成重复的子路径。
回溯三部曲
- 1. 回溯函数的参数及返回值
public void backTracing(String s, int startIndex)
- 2. 回溯终止条件
- 直到 startIndex 和 s 的长度相等,证明回溯终止
// 收集结果
if(startIndex==s.length()){
res.add(new ArrayList<>(path));
return;
}
- 3. 回溯逻辑
- 首先需要对当前回溯到的字符串做分割,每层回溯中实际上是以 startIndex 为起始点,通过 i++ 的操作遍历从起始点 往后的所有字符串的长度
- 因此每次回溯对 从起始点到当前 i 的子串进行分割,判断子串是否回文,若回文则继续回溯,不回文直接继续。
for(int i = startIndex;i<s.length();i++){
if(isPlainDrome(s,startIndex,i)){
String str = s.substring(startIndex,i+1);
path.add(str);
backTracing(s,i+1);
path.remove(path.size()-1);
}else{
continue;
}
}
2- 题解
⭐分割回文串—— 代码实现思路
class Solution {
List<List<String>> res = new ArrayList<>();
List<String> path = new ArrayList<>();
public List<List<String>> partition(String s) {
backTracing(s,0);
return res;
}
public void backTracing(String s,int startIndex){
if(startIndex==s.length()){
res.add(new ArrayList<>(path));
return;
}
for(int i = startIndex;i<s.length();i++){
if(isPlainDrome(s,startIndex,i)){
String str = s.substring(startIndex,i+1);
path.add(str);
backTracing(s,i+1);
path.remove(path.size()-1);
}else{
continue;
}
}
}
public boolean isPlainDrome(String s,int l,int r){
for(;l<r;l++,r--){
if(s.charAt(l)!=s.charAt(r)){
return false;
}
}
return true;
}
}
复杂度分析
3- ACM模式
public class hot60_partition {
static List<List<String>> res = new ArrayList<>();
static List<String> path = new ArrayList<>();
public static List<List<String>> partition(String s) {
backTracing(s,0);
return res;
}
public static void backTracing(String s,int startIndex){
if(startIndex==s.length()){
res.add(new ArrayList<>(path));
return;
}
for(int i = startIndex;i<s.length();i++){
if(isPlainDrome(s,startIndex,i)){
String str = s.substring(startIndex,i+1);
path.add(str);
backTracing(s,i+1);
path.remove(path.size()-1);
}else{
continue;
}
}
}
public static boolean isPlainDrome(String s,int l,int r){
for(;l<r;l++,r--){
if(s.charAt(l)!=s.charAt(r)){
return false;
}
}
return true;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("需要分割的字符串");
String input = sc.next();
List<List<String>> forRes = partition(input);
for(List<String> a:forRes){
System.out.print(a+" ");
}
}
}