力扣【动态规划专题】131. 分割回文串

160 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 19 天,点击查看活动详情

题目链接

131. 分割回文串 - 力扣(LeetCode)

题目描述

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

回文串 是正着读和反着读都一样的字符串。

测试用例

用例1:

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

限制

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

题目分析

题目需要我们求一个字符串可以切割为全为回文子字符串的方式有多少中可能性

以用例1 为例,采用一个伪动态规划的思路,引入一个初始长度 f(0) = '',推导他们的拼接方式如下:

f(1) = f(0) + 'a'
f(2) = f(0) + 'aa'f(1) + 'a'
f(3) = f(2) + 'b'f(1) + 'ab'f(0) + 'aab'
...

推导出来的状态转移公式则为:f(n) = f(m) + str, (str = s.slice(m, n), 0 <= m < n),我们只需要判断 str 是否为回文即可

str 为回文,我们就需要为 f(n) 记录下 f(m) + str 的组合方式,作为确认好的拼接方案,供后面的动态规划基础参数使用

代码实现

完整的代码实现如下

var partition = function (s) {
    s = s.split('')
    let arr = [];
    for (let i = 0; i < s.length + 1; i++) {
        arr.push([])
    }
    arr[0] = [['']];
    for (let i = 1; i <= s.length; i++) {
        for (let j = i - 1; j >= 0; j--) {
            // console.log(i, j, s.slice(j, i).join(''))
            let str = s.slice(j, i).join('')
            if (check(str)) {
                for (let k = 0; k < arr[j].length; k++) {
                    arr[i].push([...arr[j][k], str]);
                }
            }
        }
    }
    return arr.pop().map(n => {
        n.shift(); return n;
    });
    function check(str) {
        let l = 0, r = str.length - 1;
        let flag = true;
        for (; l < r; l++, r--) {
            if (str.charAt(l) != str.charAt(r)) {
                flag = false;
                break;
            }
        }
        return flag;
    }
};

image.png

初版未经休整的代码效率很低,起码嵌套了 3 层 for 循环,而且存在重复的数组遍历以及重复的回文字符检验

优化

这里提供另一种解决的思路,上一步的代码中,涉及到对字符串的回文校验,存在很多次重复以及无意义的校验。以字符串 abccdba 为例,他的中心点是 c,前后扩展1位,为 ccd 不是回文,那么,以 ccd 为中心,继续前后扩展的 bccdb, abccdba 都是没有必要校验的

我们可以获取字符串中所有的回文子字符串的中心点和边界,然后以此为依据从 f(0) 开始确认他的组合方式