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

一、题目描述

给你一个字符串 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. 结构清晰:标准回溯模板,面试必背

九、博客总结

  • 分割回文串是一道经典回溯题
  • 核心:枚举切割点 + 判断回文
  • 模板:选择 → 递归 → 撤销选择
  • 终止条件:切割到字符串末尾
  • 判断回文使用双指针即可
相关推荐
洛水水1 小时前
【力扣100题】55.编辑距离
算法·leetcode·动态规划
洛水水1 小时前
【力扣100题】62.滑动窗口最大值
数据结构·算法·leetcode
IronMurphy1 小时前
算法五十一 64. 最小路径和
算法
醒醒该学习了!1 小时前
Prompt提示词——带有深度思考模型的提示方法(理论篇)
人工智能·算法·prompt
君为先-bey1 小时前
Latte——视频生成的潜在扩散变换器
算法·机器学习·音视频·扩散模型
浅念-2 小时前
LeetCode刷题专题:FloodFill泛滥填充算法剖析
数据结构·算法·leetcode·职场和发展·深度优先·宽度优先
笨蛋不要掉眼泪2 小时前
Java并发编程:深入剖析 ArrayBlockingQueue
java·开发语言·算法·并发
菜菜的顾清寒2 小时前
力扣HOT100(33)二叉树的最大深度
算法·leetcode·职场和发展
Deepoch2 小时前
Deepoc数学大模型:重塑半导体研发与制造的核心算法范式
人工智能·算法·机器学习·半导体·deepoc·数学大模型