[路飞]_Leetcode 132. 分割回文串 II

142 阅读4分钟

「携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

今天我们来做一下leetcode中的一道困难题,听说也是腾讯的面试题😍

image.png

题目链接在这里 132. 分割回文串 II

题意

image.png

思路

这道题使用到了动态规划。

image.png

如上图所示,假设现在找到了一个位置j,j到n之间是一个回文串,字符串s的长度为n 定义dp[i]表示s中前i个字符能够分割成回文字串的最小数目,那么,可以退出如如下关系 dp[n] = dp[j] + 1 表示:前n个字符的最小的分割回文串的数目 = 前j个字符的最小的分割回文串的数目 + 最后一个回文串

知道了这些之后,我们发现如果可以枚举每个位置结尾可能产生的回文串,就可以将求解当前字符串的问题转化成一个求前面部分字符串的 最小分割回文串数目的问题,也就实现了状态的转移

  • ind[i] 记录前i个字符,可以与结尾形成回文串的字符位置 例如 a a a b 中
    • ind[1] = [0] 表示前1个字符,也就是a中 下标0到结尾可以组成一个回文串 a
    • ind[2] = [0,1] 表示前2个字符,也就是a a中 下标0和下标1到结尾可以组成一个回文串 a aa
    • ind[3] = [0,1,2] 表示前3个字符,也就是a中 下标0 和 下标1 和 下标2 到结尾可以组成一个回文串 a aa aaa
    • ind[4] = [3] 表示前4个字符,也就是aaab中 下标3到结尾可以组成一个回文串b

知道了这些之后,我们就可以着手尝试进行代码实现。

  • 首先可以定义一个extract方法,用于寻找前i个字符中,哪些位置到结尾可以形成一个回文串,每次形成一个新的回文串,将回文串的起始位置i记录到j+1中,为什么是j+1呢?
    • 那是因为这里定义的ind[i]表示前i个字符中 哪些位置到s的结尾可以形成回文串,而j是回文串的结尾下标,前j+1个字符的结尾下标是j。
// 提取字符串s中,前i个字符中 哪些位置可以和结尾形成回文串
    let ind = Array(n+1).fill().map(() => [])
    const extract = (s, i, j, ind) => {
        while (i >= 0 && s[i] == s[j]) {
            // 每一次首位相等,相当于一个新的回文串,需要记录下来
            // 下标i~j是一个回文串,是前j+1个字符的结尾
            ind[j + 1].push(i)
            i--, j++
        }
    }
  • 然后,将每个位置的结尾可能产生回文串的下标都初始化出来,便于在后续状态转移过程中使用
for (let i = 0; i < n; i++) {
        // 提取以i或者i/i+1 为中心的所有回文子串的位置信息
        extract(s, i, i, ind)
        extract(s, i, i + 1, ind)
    }
  • 最后,就可以实现状态的转移过程了,如果前i个字符中 下标j到结尾可以形成回文串,那么字符串可以分为0~j-1前j个字符和最后一个回文子串,即 dp[i] = Math.min(dp[i],dp[j]+1)
let dp = Array(n + 1).fill(Infinity)
    // 前0个字符,也就是空字符,被分割的字串数目当然是0了
    dp[0] = 0
    for (let i = 1; i <= n; i++) {
        // 遍历前i个字符中,可以和结尾形成回文串的所有位置
        for (let j of ind[i]) {
            // 下标j到结尾是一个回文串,所以字符串可以分为0~j-1前j个字符和最后一个回文子串
            dp[i] = Math.min(dp[i], dp[j] + 1)
        }
    }

这样,我们就解决了这道题。

代码实现

image.png

结束语

做过这道题之后,可以发现,动态规划类的题目思考起来 还是挺让人掉头发的🤣

如果有更好的分析思路,欢迎大家在评论区发表看法!⛄