力扣hot100 分割回文串 -回溯

这道题是回溯算法的经典应用题,完全可以用你已经掌握的两套回溯模板(「选或不选」「枚举选哪个」)来解决,只是把 "选数字" 换成了 "在哪里切分字符串"。我们从本质到代码一步步拆解。

一、先搞懂题目本质

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)两个选择分支
  1. 不分割:不在这个间隙下刀,子串继续延长

    • 条件:i 不是最后一个字符(最后一个字符后面必须收尾,不存在 "不分割")
    • 操作:i+1start 不变,递归下一层
  2. 分割 :在 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;
    }
};