求字符串的最长回文字串--动态规划四部曲

68 阅读4分钟

最长回文子串--动态规划四部曲

先看题目描述:

给你一个字符串 s,找到 s 中最长的回文子串。

如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

示例 1:

输入: s = "babad"
输出: "bab"
解释: "aba" 同样是符合题意的答案。

示例 2:

输入: s = "cbbd"
输出: "bb"

1. 确定dp数组(dp table)以及下标的含义

首先我们从后往前看,一个下标为i到j的字符串s是不是回文字符串,必须要满足两个条件: (1)s[i]==s[j];

(2)下标为s[i+1]到s[j-1]的字符串是回文字符串;

所以这里用一个二维数组来进行状态的转移,初始化先都填充为false,因为都会从长度为1的状态进行转移,待会在代码中会对长度为一的情况进行处理:

dp=Array.from(Array(s.length+1),()=>Array(s.length+1))//s.length+1纯属个人习惯,其实这里s.length就可以了

这里dp[i][j]的含义就是下标为i到j的字符串是否回文,是就等于true,不是就等于false

2.确定状态转移方程

if(i==j){
    //这里dp[i][j]直接赋值为true
    dp[i][j]==true
}else if(i==j-1){
    //判断i和j相邻的情况
    if(s[i]==s[j]){
        dp[i][j]=true
    }else{
        dp[i][j]=false
    }
}else{
    //最后就是其他情况啦
    //首先考虑i==j的情况
    if(s[i]==s[j]){
        //如果i+1到j-1也是回文字符串,即dp[i+1][j-1]==true
        if(dp[i+1][j-1]){
            dp[i][j]=true
        }else{
            dp[i][j]=false        
        }
    }else{
        //如果s[i]!=s[j],那么s[i]到s[j]肯定不是回文字符串啦
        dp[i][j]=false
    }  
}

到这里有同学可能就有疑惑了,题目不是让求出最长回文子字符串吗?别急,既然我们已经判断出了任意子串是不是回文字符串,那么只需要在创建一个变量result,在每次判断字符串回文时候时对当前result长度和回文子串长度进行比较,选取较长的赋值给result变量进行保存就可以了,代码如下:

let result=''
if(i==j){
    //这里dp[i][j]直接赋值为true
    dp[i][j]==true
    if(result.length<1){
        result=s[i]
    }
}else if(i==j-1){
    //判断i和j相邻的情况
    if(s[i]==s[j]){
        dp[i][j]=true
        if(result.length<2){
            result=s[i]+[j]
        }
    }else{
        dp[i][j]=false
    }
}else{
    //最后就是其他情况啦
    //首先考虑i==j的情况
    if(s[i]==s[j]){
        //如果i+1到j-1也是回文字符串,即dp[i+1][j-1]==true
        if(dp[i+1][j-1]){
            dp[i][j]=true
            if(result.length<j-i+1){
                result=s.substring(i,j-i+1)
            }
        }else{
            dp[i][j]=false        
        }
    }else{
        //如果s[i]!=s[j],那么s[i]到s[j]肯定不是回文字符串啦
        dp[i][j]=false
    }  
}

3.dp数组初始化

初始化操作我将其放到了遍历的时候进行单独的判断,所以这里就省略了单独初始化操作步骤

4.确定遍历顺序

刚学动态规划时候,我也对遍历顺序的确定懵懵懂懂,其实遍历顺序的确定与状态转移方程密不可分, 这里状态转移方程dp[i][j]的状态是由dp[i+1][j-1]和s[i]是否等于s[j]来确定的:

i\jj=0j=1j=2j=3
i=0falsefalsefalsefalse
i=1falsefalsefalsefalse
i=2falsefalsefalsefalse
i=3falsefalsefalsefalse

比如当i=0,j=3时候: dp[0][3]等于true还是false取决于dp[1][2]是true还是false,所以dp[0][3]是由dp[1][2]推导来的,遍历顺序自然就是下往上,从前往后

 for (let i = n - 1; i >= 0; i--) {
        for (let j = i; j < n; j++) {
        //...
        }
    }

最终题解:

var longestPalindrome = function (s) {
    let n = s.length
    let dp = Array.from(Array(n + 1), () => Array(n + 1).fill(0))
    let res = ''
    for (let i = n - 1; i >= 0; i--) {
        for (let j = i; j < n; j++) {
            if (i == j) {
                dp[i][j] = true
                if (res.length < 1) {
                    res = s[i]
                }
            } else if (i == j - 1) {
                if (s[i] == s[j]) {
                    dp[i][j] = true
                    if (res.length < 2) {
                        res = s[i] + s[j]
                    }
                } else {
                    dp[i][j] = false
                }
            } else {
                if (s[i] == s[j]) {
                    if (dp[i + 1][j - 1]) {
                        dp[i][j] = true
                        if (res.length < j-i+1) {
                            res = s.substring(i, j + 1)
                        }
                    } else {
                        dp[i][j] = false
                    }
                } else {
                    dp[i][j] = false
                }
            }
        }
    }
    return res
};

总结

到这里本篇文章就结束了,其实写这篇文章最初是因为今天下午收到了蔚来的笔试邀请,那当即就搜索一波蔚来笔试面经啊,看看大体会出哪些题目有一个心理准备,就看到了某个作者说笔试时遇到了这道题,我就想想自己能不能思考出来,毕竟刷了不少动态规划题目,也算是对自己这段时间算法学习动态篇的一个考验与总结,并不是说就是这道题最好的解法,只是分享一下自己的思考。如果有想学习算法而又不知道茫茫题海该如何选择的同学,推荐大家去代码随想录进行学习哟,一个很赞的算法学习网站。