
这道题是回溯算法的经典应用题,完全可以用你已经掌握的两套回溯模板(「选或不选」「枚举选哪个」)来解决,只是把 "选数字" 换成了 "在哪里切分字符串"。我们从本质到代码一步步拆解。
一、先搞懂题目本质
1. 问题拆解
给一个字符串 s,我们要在字符之间的间隙下刀切割,把字符串切成若干段,要求每一段子串都必须是回文串,返回所有合法的切割方案。
举个例子:s = "aab",长度为 3,字符之间有 2 个间隙(第一个 a 和第二个 a 之间、第二个 a 和 b 之间)。
- 每个间隙有两种选择:切 或者 不切
- 总共有
2^(n-1)种切割方式,我们需要从中筛选出「所有子串都是回文」的合法方案。
2. 前置工具:判断一个子串是不是回文
这是本题的基础校验函数,用双指针法即可:
- 左指针从子串起点、右指针从子串终点同时向中间移动
- 只要有一对字符不相等,就不是回文;指针相遇都相等,就是回文
二、方法一:输入视角(间隙选或不选)
对应你之前学的 **「选或不选」回溯模型 **,和组合总和的思路完全同源。
1. 思路来源
字符串有 n-1 个间隙,我们从左到右逐个处理每个间隙:
- 不选(不切):当前子串继续往后延长,不分割
- 选(切一刀):当前子串结束,校验它是不是回文,合法就存入路径,然后从下一个字符开始新的子串
2. 递归三要素
(1)递归参数
i:当前遍历到的字符下标(正在处理 i 和 i+1 之间的间隙)start:当前正在拼接的子串的起始下标
(2)边界条件
当 i == n(遍历完所有字符),说明一整段字符串分割完成,且每一段都经过了回文校验,此时把 path 加入答案,返回。
(3)两个选择分支
-
不分割:不在这个间隙下刀,子串继续延长
- 条件:
i不是最后一个字符(最后一个字符后面必须收尾,不存在 "不分割") - 操作:
i+1,start不变,递归下一层
- 条件:
-
分割 :在 i 位置切一刀,把
[start, i]作为一段- 前提:
[start, i]必须是回文串(不合法直接剪掉,不递归) - 操作:把子串加入
path→ 递归i+1,新的start = i+1→ 递归回来后pop_back恢复现场
- 前提:
3. 示例模拟(s = "aab",n=3)
初始:i=0, start=0, path=[]
plaintext
dfs(i=0, start=0)
├─ 不分割 → dfs(i=1, start=0)
│ ├─ 不分割 → dfs(i=2, start=0)
│ │ └─ 必须分割:校验[0,2]="aab",不是回文,剪枝,不递归
│ └─ 分割:校验[0,1]="aa"是回文
│ path=["aa"], dfs(i=2, start=2)
│ └─ 必须分割:校验[2,2]="b"是回文
│ path=["aa","b"], dfs(i=3, start=3)
│ → i==n,存入答案
│ → 回溯pop("b") → 回溯pop("aa")
└─ 分割:校验[0,0]="a"是回文
path=["a"], dfs(i=1, start=1)
├─ 不分割 → dfs(i=2, start=1)
│ └─ 分割:校验[1,2]="ab",不是回文,剪枝
└─ 分割:校验[1,1]="a"是回文
path=["a","a"], dfs(i=2, start=2)
└─ 分割:校验[2,2]="b"是回文
path=["a","a","b"], dfs(i=3, start=3)
→ i==n,存入答案
→ 回溯pop("b") → 回溯pop("a") → 回溯pop("a")
最终得到两个合法方案:["aa","b"] 和 ["a","a","b"],和示例完全一致。
cpp
class Solution {
public:
vector<vector<string>> partition(string s) {
int n = s.size();
vector<vector<string>> ans;
vector<string> path;
auto dfs = [&](this auto&& dfs,int i,int start){
//边界 遍历完所有的字符 得到一个合法分割方案
if(i == n){
ans.push_back(path);
return;
}
//选择1
if(i<n-1){
dfs(i+1,start);
}
//选择2:
if(isHui(s,start,i)){
path.push_back(s.substr(start,i-start+1));
dfs(i+1,i+1);
path.pop_back();
}
};
dfs(0,0);
return ans;
}
bool isHui(string& s,int left,int right){
while(left<right){
if(s[left]!=s[right]) return false;
left++;
right--;
}
return true;
}
};