(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];
    }
}

思路

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

相关推荐
梵刹古音3 小时前
【C语言】 指针与数据结构操作
c语言·数据结构·算法
VT.馒头3 小时前
【力扣】2695. 包装数组
前端·javascript·算法·leetcode·职场和发展·typescript
源代码•宸4 小时前
大厂技术岗面试之谈薪资
经验分享·后端·面试·职场和发展·golang·大厂·职级水平的薪资
马猴烧酒.4 小时前
【面试八股|JVM虚拟机】JVM虚拟机常考面试题详解
jvm·面试·职场和发展
CoderCodingNo5 小时前
【GESP】C++五级练习题 luogu-P1865 A % B Problem
开发语言·c++·算法
大闲在人5 小时前
7. 供应链与制造过程术语:“周期时间”
算法·供应链管理·智能制造·工业工程
小熳芋5 小时前
443. 压缩字符串-python-双指针
算法
Charlie_lll5 小时前
力扣解题-移动零
后端·算法·leetcode
chaser&upper5 小时前
矩阵革命:在 AtomGit 解码 CANN ops-nn 如何构建 AIGC 的“线性基石”
程序人生·算法