今日算法(带回文问题的回溯)

一、题目描述

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文串。 返回所有可能的分割方案。

示例 1 输入:s = "aab" 输出:[["a","a","b"],["aa","b"]]

示例 2 输入:s = "a" 输出:[["a"]]

示例 3 输入:s = "aaa" 输出:

复制代码
[
    ["a","a","a"],
    ["a","aa"],
    ["aa","a"],
    ["aaa"]
]

二、解法:纯回溯法(最易懂版本)

核心思路(一句话)

在字符串的空隙里尝试 "切一刀",只要切出来的子串是回文,就继续往下切;直到切完整个字符串,就记录一种方案。


回溯三要素(必背)

  1. 选择:从当前位置开始,截取一段子串
  2. 限制 :截取的子串必须是回文
  3. 结束:截取到字符串末尾,记录答案

三、完整代码(C++)

复制代码
#include <vector>
#include <string>
using namespace std;

class Solution {
public:
    vector<vector<string>> partition(string s) {
        vector<vector<string>> result;  // 存放最终所有答案
        vector<string> path;            // 存放当前正在分割的方案
        backtrack(s, 0, path, result);
        return result;
    }

private:
    // 回溯函数
    // start: 从哪个下标开始分割
    void backtrack(const string& s, int start, 
                   vector<string>& path, 
                   vector<vector<string>>& result) {
        
        // 1. 终止条件:已经分割到字符串末尾 → 记录答案
        if (start == s.size()) {
            result.push_back(path);
            return;
        }

        // 2. 尝试所有可能的分割位置:从 start 开始,到 i 结束
        for (int i = start; i < s.size(); ++i) {
            // 判断 s[start ... i] 是否是回文
            if (isPalindrome(s, start, i)) {
                // 是回文 → 加入当前路径
                path.push_back(s.substr(start, i - start + 1));
                
                // 3. 递归:继续分割下一段
                backtrack(s, i + 1, path, result);
                
                // 4. 回溯:撤销选择
                path.pop_back();
            }
        }
    }

    // 判断 s[left ... right] 是否是回文串
    bool isPalindrome(const string& s, int left, int right) {
        while (left < right) {
            if (s[left] != s[right]) return false;
            left++;
            right--;
        }
        return true;
    }
};

四、代码逐行解释(博客重点)

1. 主函数

复制代码
backtrack(s, 0, path, result);
  • 从下标 0 开始分割
  • path 记录当前分割方案
  • result 收集所有合法答案

2. 终止条件

复制代码
if (start == s.size())

表示已经把整个字符串分割完成 ,此时 path 就是一个合法方案,存入结果。

3. 循环尝试所有分割点

复制代码
for (int i = start; i < s.size(); i++)
  • start:本次分割的起点
  • i:本次分割的终点
  • 截取子串:s[start ... i]

4. 只选择回文子串

复制代码
if (isPalindrome(s, start, i))

只有子串是回文,才继续递归。

5. 回溯核心

复制代码
path.push_back(...)
backtrack(...)
path.pop_back();
  • 先选
  • 再递归
  • 最后撤销选择,尝试下一种分割方式

五、案例 1:s = "aab" 完整推演(最经典)

字符串:a a b 下标:0 1 2

第一层:start = 0

尝试分割:

  • [0,0] = "a" ✅ 回文
  • [0,1] = "aa" ✅ 回文
  • [0,2] = "aab" ❌ 不是回文

分支 1:选 "a" → start = 1

第二层:start = 1

  • [1,1] = "a" ✅ 回文 → start = 2
    • [2,2] = "b" ✅ 回文 → start = 3(结束) → 得到方案:["a","a","b"]

分支 2:选 "aa" → start = 2

第二层:start = 2

  • [2,2] = "b" ✅ 回文 → start = 3(结束) → 得到方案:["aa","b"]

最终结果

plaintext

复制代码
[["a","a","b"], ["aa","b"]]

六、案例 2:s = "aaa" 推演(帮你彻底吃透)

所有可能分割:

  1. a | a | a
  2. a | aa
  3. aa | a
  4. aaa

回溯过程

  1. start=0 选 a → start=1 选 a → start=2 选 a
  2. start=0 选 a → start=1 选 aa
  3. start=0 选 aa → start=2 选 a
  4. start=0 选 aaa

最终输出 4 种方案。


七、案例 3:s = "ab"

  • 尝试 [0,0] = "a" → 剩下 [1,1] = "b" → 得到 ["a","b"]
  • 尝试 [0,1] = "ab" ❌ 不是回文 结果:[["a","b"]]

八、回溯法为什么正确?

  1. 不重不漏:枚举所有合法分割点
  2. 剪枝:不是回文就直接跳过,不递归
  3. 结构清晰:标准回溯模板,面试必背

九、博客总结

  • 分割回文串是一道经典回溯题
  • 核心:枚举切割点 + 判断回文
  • 模板:选择 → 递归 → 撤销选择
  • 终止条件:切割到字符串末尾
  • 判断回文使用双指针即可
相关推荐
Cx330_FCQ3 小时前
Tmux使用
服务器·git·算法
拳里剑气4 小时前
C++算法:多源BFS
c++·算法·宽度优先·多源bfs
ysa0510305 小时前
【板子】短序列dp(换成维护更小常数维度的dp)
c++·笔记·算法·板子
shwill1235 小时前
PID 算法(三)--- 增量 PID ↔ 单神经元 PID 等价映射
linux·算法
wabs6666 小时前
关于图论【卡码网110.字符串迁移的思考】
数据结构·算法·图论
hanlin037 小时前
刷题笔记:力扣第242、349题(哈希表)
笔记·算法·leetcode
浮沉9878 小时前
二分查找算法例题(二)
算法
only-qi8 小时前
大模型Agent面试攻略:落地工程痛点、评估体系与Agentic RAG核心精讲
人工智能·算法·面试·职场和发展·langchain·rag
Gauss松鼠会10 小时前
【GaussDB】GaussDB锁阻塞源头查询
java·开发语言·前端·数据库·算法·gaussdb·经验总结
oier_Asad.Chen10 小时前
【洛谷题解/AcWing题解】洛谷P4011 孤岛营救问题/AcWing1131拯救大兵瑞恩
数据结构·笔记·学习·算法·动态规划·图论·宽度优先