(leetcode)力扣100 61分割回文串(回溯,动归)

题目

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

数据范围

1 <= s.length <= 16

s 仅由小写英文字母组成

测试用例

示例1

java 复制代码
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]

示例2

java 复制代码
输入:s = "a"
输出:[["a"]]

题解1(官解1,时间On 2n,空间On2)

java 复制代码
class Solution {
    // res 用于存储最终所有可能的分割方案(结果列表)
    List<List<String>> res = new ArrayList<>();
    // curr 用于存储当前回溯路径中已经切分出来的回文子串
    List<String> curr = new ArrayList<>();
    // isPd[i][j] 表示字符串从索引 i 到 j 的子串是否为回文串
    boolean isPd[][];
    int n;

    public List<List<String>> partition(String s) {
        n = s.length();
        // 初始化 DP 表,n+1 是为了防止 i+1 或 j-1 越界时的溢出
        isPd = new boolean[n + 1][n + 1];
        
        // 预处理第一步:填充默认值
        // 将所有位置设为 true,是因为当子串长度为 1 (i==j) 或长度为 0 (i>j) 时,它们逻辑上是回文的基础
        for (int i = 0; i < n; i++) {
            Arrays.fill(isPd[i], true);
        }

        // 预处理第二步:动态规划填表
        // 状态转移方程:s[i...j] 是回文 <=> (s[i] == s[j]) 且 (内部子串 s[i+1...j-1] 也是回文)
        // 注意:i 需要从大到小遍历,因为计算 isPd[i] 需要用到 i+1 的结果
        for (int i = n - 1; i >= 0; i--) {
            for (int j = i + 1; j < n; j++) {
                isPd[i][j] = (s.charAt(i) == s.charAt(j)) && isPd[i + 1][j - 1];
            }
        }

        // 从索引 0 开始进行深度优先搜索(回溯)
        dfs(s, 0);
        return res;
    }

    /**
     * 回溯函数:尝试从 pos 位置开始切分字符串
     */
    public void dfs(String s, int pos) {
        // 递归出口:如果起始位置已经到达字符串末尾,说明找到了一组完整的分割方案
        if (pos == n) {
            // 注意:必须 new 一个新列表存入 res,否则 curr 的变化会影响已存入的结果
            res.add(new ArrayList<String>(curr));
            return;
        }

        // 从当前位置 pos 开始,尝试所有可能的切分点 j
        for (int j = pos; j < n; j++) {
            // 利用预处理好的 DP 表,在 O(1) 时间内判断当前切下的子串 s[pos...j] 是否回文
            if (isPd[pos][j]) {
                // 1. 做选择:将当前回文子串加入路径
                curr.add(s.substring(pos, j + 1));
                
                // 2. 递归:从 j+1 的位置继续向后切分
                dfs(s, j + 1);
                
                // 3. 撤销选择:回溯,移除最后一个切片,尝试下一种切分可能
                curr.remove(curr.size() - 1);
            }
        }
    }
}

题解2(官解2,时空同上)

java 复制代码
class Solution {
    int[][] f;
    List<List<String>> ret = new ArrayList<List<String>>();
    List<String> ans = new ArrayList<String>();
    int n;

    public List<List<String>> partition(String s) {
        n = s.length();
        f = new int[n][n];

        dfs(s, 0);
        return ret;
    }

    public void dfs(String s, int i) {
        if (i == n) {
            ret.add(new ArrayList<String>(ans));
            return;
        }
        for (int j = i; j < n; ++j) {
            if (isPalindrome(s, i, j) == 1) {
                ans.add(s.substring(i, j + 1));
                dfs(s, j + 1);
                ans.remove(ans.size() - 1);
            }
        }
    }

    // 记忆化搜索中,f[i][j] = 0 表示未搜索,1 表示是回文串,-1 表示不是回文串
    public int isPalindrome(String s, int i, int j) {
        if (f[i][j] != 0) {
            return f[i][j];
        }
        if (i >= j) {
            f[i][j] = 1;
        } else if (s.charAt(i) == s.charAt(j)) {
            f[i][j] = isPalindrome(s, i + 1, j - 1);
        } else {
            f[i][j] = -1;
        }
        return f[i][j];
    }
}

思路

这道题稍微比前几道题难一些,主要是考了一些回文的知识,并且考察了如何处理重复计算的方法,本文的题解一是通过动态规划提前预处理,题解二是通过记忆数组,两个方法都可以,动态规划代码简单一些,看个人吧,回溯思想都老生常谈了,就不重复讲解了。

相关推荐
文丑颜不良啊1 小时前
LeetCode2821~2840题目解析
java·leetcode
YLCHUP2 小时前
【思维&图论建模】题解:债权(debt)_图论_建模_思路转化_思维题目_C++算法竞赛
c++·笔记·算法·数学建模·贪心算法·深度优先·图论
思麟呀3 小时前
背包DP(01背包+完全背包)
c++·算法
笑容掩饰我的失落4 小时前
区块链从小白到精通(七):Solidity 语法精讲——数据类型、函数、修饰器与事件
算法·区块链·php·智能合约·哈希算法
Pniubi5 小时前
力扣24. 两两交换链表中的节点(递归)
算法·leetcode·链表
꧁细听勿语情꧂5 小时前
直接选择排序,堆排序,冒泡排序,递归快速排序、hoare版本、挖坑法、lomuto前后指针,非递归快速排序
c语言·开发语言·数据结构·算法
文丑颜不良啊6 小时前
LeetCode2741~2760题目解析
java·leetcode
俊俊几年的6 小时前
动态规划(Dynamic Programming)详解:从入门到 LeetCode Hot 100
linux·c++·算法·leetcode·嵌入式·动态规划·c
小雉系统15 小时前
BcoreOS安全通信架构:FsSession 抗算法后门安全设计
算法·安全·架构