leetcode-5-最长回文子串

1,548 阅读1分钟

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

题目地址

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

示例 1:

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

示例 2:

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

 

提示:

  • 1 <= s.length <= 1000
  • s 仅由数字和英文字母组成

本文题意很简单,想要解出来也不难,遍历所有可能的子串,判断是否是回文即可,但是该算法的时间复杂度为 O(n´³),时间复杂度太高,会超出时间限制,接下来就让我为您介绍两种时间复杂度为 O(n²)的算法。

解题思路-动态规划

所谓回文字符串,就是从两端向中间或者从中间向两端,每个字符两两相等,由此我们可以知道,如果从下标 i 到 下标 j 的子串是回文串,并且 s[i-1]===s[j+1],则下标 i-1 到下标 j+1 的子串也是回文串。由以上推导,我们可以定义出动归的状态定义和转移方程。
状态定义dp[i][j] 表示下标 i 到下标 j 的子串是否是回文串
转移方程dp[i][j] = dp[i+1][j-1] && s[i]===s[j]
又因为单个字符都是回文串,所以我们可以基于这个条件初始化所有的 dp[i][i] = true,然后不断的扩展子串长度,直到推导出所有的子串结果。

代码实现

var longestPalindrome = function(s) {
    const len = s.length

    if(len===1){
        return s
    }

    const dp = Array(len)

    for(let i = 0;i<len;i++){
        dp[i] = []
        dp[i][i] = true
    }

    let maxLen = 1
    let beginIndex = 0

    for(let L = 2;L<=len;L++){
        for(let i = 0;i<=len-L;i++){
            const j = i+L-1

            if(s[i]!==s[j]){
                dp[i][j] = false
            }else{
                if(i===j-1){
                    dp[i][j] = true
                }else{
                    dp[i][j] = dp[i+1][j-1]
                }
            }

            if(dp[i][j] && j-i+1>maxLen){
                maxLen = j-i+1
                beginIndex = i
            }
        }
    }

    return s.substring(beginIndex,beginIndex+maxLen)
}

解题思路-中心扩展

有了动归解题思路的讲解,我们知道可以如果下标 i 到下标 j 的子串是回文串,则如果 s[i-1]===s[j+1],下标 i-1 到下标 j+1 的子串也是回文串。由此可以想到可以枚举所有可能的回文串中心,通过向两侧扩展的方式,找到所有的回文子串,从而得到最长的回文子串。

代码实现

function getMaxBackTextString(s,len,l,r){
    if(s[l]!==s[r]){
        return 0
    }
    let res = r-l+1
    while(l>0 && r<len-1){
        l--
        r++
        if(s[l]===s[r]){
            res+=2
        }else{
            break
        }
    }

    return res
}
var longestPalindrome = function(s) {
    const len = s.length
    if(len===1){
        return s
    }

    let maxLen = 1
    let beginIndex = 0

    for(let i = 0;i<len;i++){
        const len1 = getMaxBackTextString(s,len,i,i)
        const len2 = getMaxBackTextString(s,len,i,i+1)

        if(len1>maxLen){
            maxLen = len1
            beginIndex = i-(len1-1)/2
        }
        if(len2>maxLen){
            maxLen = len2
            beginIndex = i-(len2-2)/2
        }
    }

    return s.substring(beginIndex,beginIndex+maxLen)
}

需要注意的是,虽然都是O(n²)的时间复杂度,但是中心扩展要比动态规划更高效,这是因为动态规划初始化的过程是 O(n),而且它的推导过程是完整的。而中心扩展法遇到无法扩展就停止了,而且每次扩展是两个字符长度。
还有一部分原因是动态规划的空间复杂度也是O(n²),而中心扩展的空间复杂度仅为O(1).

至此我们就完成了 leetcode-5-最长回文子串

如有任何问题或建议,欢迎留言讨论!