算法练习day49

73 阅读2分钟

回文子串

暴力解法

两层for循环,遍历区间起始位置和终止位置,还需一层遍历判断这个区间是不是回文,时间复杂度是On^3

动态规划

五部曲

  1. 确定dp数组,基于回文的特性,我们需要把dp数组定义成二维dp数组,dp[i][j],区间[i, j]的子串是否是回文子串
  2. 确定递推公式
    1. s[i] !== s[j],dp[i][j] = false
    2. s[i] === s[j]
      1. i===j,一个字符,是回文子串
      2. 当i和j相差1,是回文子串
      3. 当i和j相差大于1,依赖于[i+1, j-1]区间是不是回文子串
if(s[i] !== s[j]) {
    dp[i][j] = false
} else {
    if(j - i <= 1) {
        result++
        dp[i][j] = true
    } else if(dp[i+1][j-1]) {
        result++
        dp[i][j] = true
    }
}
  1. dp数组初始化,都初始化为false
  2. 确定遍历顺序,dp[i+1][j-1]dp[i][j]的左下角,所以一定要从下到上,从左到右遍历
  3. 举例推导

时间复杂度:O(n^2) 空间复杂度:O(n^2)

/**
 * @param {string} s
 * @return {number}
 */
var countSubstrings = function(s) {
    let result = 0
    let dp = new Array(s.length).fill(0).map(_ => new Array(s.length).fill(false))
    for(let i = s.length - 1; i >= 0;i--) {
        for(let j = i;j < s.length;j++) {
            if(s[i] !== s[j]) {
                dp[i][j] = false
            } else {
                if(j - i <= 1) {
                    result++
                    dp[i][j] = true
                } else if(dp[i+1][j-1]){
                    result++
                    dp[i][j] = true
                }
            }
        }
    }
    return result
};

双指针法

找中心然后向两边扩散看是不是对称

以一个元素为中心点或者以两个元素为中心点

时间复杂度:O(n^2) 空间复杂度:O(1)

/**
 * @param {string} s
 * @return {number}
 */
var countSubstrings = function (s) {
    function extend(i, j) {
        let res = 0
        while (i >= 0 && j < s.length && s[i] === s[j]) {
            i--
            j++
            res++
        }
        return res
    }
    let result = 0
    for (let i = 0; i < s.length; i++) {
        result += extend(i, i)
        result += extend(i, i+1)
    }
    return result
};

最长回文子序列

和回文子串的区别在于

回文子序列不是连续的,回文子串是要连续的

五部曲

  1. 确定dp数组以及下标的含义,dp[i][j]表示字符串在[i,j]内的最长回文子序列的长度
  2. 确定递推公式
    1. s[i] === s[j],则dp[i][j] = dp[i+1][j-1] + 2
    2. s[i] !== s[j]
      1. 加入s[i],不要s[j],dp[i][j-1]
      2. 加入s[j],不要s[i],dp[i+1][j]
      3. dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1])
  3. 初始化,当i等于j的时候,初始化为1,其他情况初始化为0
for(let i=0; i < s.length; i++) {
    dp[i][i] = 1
}
  1. 确定遍历顺序, 从下到上遍历,从左到右遍历
  2. 举例推导

时间复杂度: O(n^2) 空间复杂度: O(n^2)

/**
 * @param {string} s
 * @return {number}
 */
var longestPalindromeSubseq = function (s) {
    let dp = new Array(s.length).fill(0).map(_ => new Array(s.length).fill(0))
    for (let i = 0; i < s.length; i++) dp[i][i] = 1
    for(let i = s.length - 1; i >=0;i--) {
        for(let j = i + 1; j < s.length;j++) {
            if(s[i] === s[j]) {
                dp[i][j] = dp[i+1][j-1] + 2
            } else {
                dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1])
            }
        }
    }
    return dp[0][s.length-1]
};