131. 分割回文串

0 阅读3分钟

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

示例 1:

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

示例 2:

输入: s = "a"
输出: [["a"]]

提示:

  • 1 <= s.length <= 16
  • s 仅由小写英文字母组成

1. 生活案例:切“对称”的甘蔗

想象你有一根很长的甘蔗(字符串),上面刻着一些字母。老板要求你把它切成几小段。

  • 规则:每一段必须是对称的(比如 abacca)。

  • 过程

    1. 你先试着从前面切下一小块。
    2. 如果这一块是对称的(回文),你就把这一块放进你的篮子里(path),然后对剩下的甘蔗重复这个过程。
    3. 如果剩下的甘蔗也能切成全是回文的段,你就成功找到了一种方案。
    4. 关键点(回溯) :如果你切完发现后面没法切出回文了,你会把刚才放进篮子里的那一块拿出来,试着切一个更长一点的块试试。

2. 代码实现与详细注释

这是你图片中的代码,我为你添加了回溯逻辑的详细中文注释:

JavaScript

/**
 * @param {string} s
 * @return {string[][]}
 */
var partition = function(s) {
    let res = [];   // 【总仓库】:存放所有成功的分割方案
    let path = [];  // 【当前篮子】:存放当前正在尝试的分割组合

    // 辅助函数:判断是不是回文(对称)
    // 就像检查甘蔗段左右两边字母是否对应
    function isPalindrome(str, left, right) {
        while (left < right) {
            if (str[left] !== str[right]) {
                return false; // 不对称
            }
            left++;
            right--;
        }
        return true; // 是回文
    }

    // 核心函数:回溯(递归)
    function backtrack(start) {
        // 【终止条件】:如果切到了甘蔗的最末尾,说明这是一种成功的方案
        if (start === s.length) {
            res.push([...path]); // 把篮子里的组合复制一份存入仓库
            return;
        }

        // 从当前位置 start 开始,尝试往后切每一个可能的长度 i
        for (let i = start; i < s.length; i++) {
            // 如果从 start 到 i 这一段是回文
            if (isPalindrome(s, start, i)) {
                // 1. 【做选择】:切下这一段,放进篮子
                path.push(s.substring(start, i + 1));
                
                // 2. 【递归】:去切剩下的那部分甘蔗
                backtrack(i + 1);
                
                // 3. 【撤销选择】:也就是回溯。
                // 把最后切的那块拿出来,让循环继续,尝试切一个更长的块。
                path.pop();
            }
        }
    }

    backtrack(0); // 从位置 0 开始切
    return res;
};

3. 核心原理解析

为什么需要 path.pop()

这就是回溯的灵魂。

假设字符串是 "aab"

  1. 第一步切下第一个 "a",放入篮子,path = ["a"]
  2. 递归去处理后面的 "ab"
  3. 发现后面可以切出 "a""b"。得到一种方案 ["a", "a", "b"]
  4. 关键时刻:为了寻找其他方案(比如开头直接切 "aa"),你必须把篮子里最后的那个 "b" 拿出来,再把 "a" 拿出来。这就是 pop,它让程序能够回到之前的状态,去尝试 i 增加后的新可能。

递归树的视角

这个过程可以看作一棵树:

  • 每一层:决定当前这一刀切在哪里。
  • 分叉:如果切下的部分是回文,就分出一个支流往下走。
  • 尽头:如果切不动了且甘蔗用完了,记录结果;如果切下的不是回文,这个分叉就死掉。

复杂度分析

  • 时间复杂度:最坏情况下是 O(N2N)O(N \cdot 2^N),因为每个位置都可以选切或不切,且每次都要判断回文。
  • 空间复杂度O(N)O(N),主要是递归栈的深度和 path 篮子的长度。