前端刷题路-Day90:回文子串(题号647)

561 阅读4分钟

这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战

回文子串(题号647)

题目

给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

示例 1:

输入:"abc"
输出:3
解释:三个回文子串: "a", "b", "c"

示例 2:

输入:"aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"

提示:

输入的字符串长度不会超过 1000 。

链接

leetcode-cn.com/problems/pa…

解释

这题啊,这题是经典排列组合。

看题意,最简单的办法就是排列组合了,求出所有的可能性,放到一个数组中,最后返回数组的长度即可。

记得之前有做过类似的题目,求某一个位置的回文串最大长度其实很简单——所有位置递归一次。

递归函数用来求从当前位置开始,向两侧扩散的最大回文串,但这个递归参数不能是简简单单的一个index,而是leftright两个位置,为什么?因为可能是当前一个位置的值,也有可能是两个相同的连续字符串,需要考虑到这两种情况。

因为,每次位置需要进行两次递归操作,一次leftright相同,都是当前index,一次right是当前indexleftindex - 1。在处理完每个位置的可能性之后答案就出来了。

这种方法显然可以解决问题,但空间复杂度略高,比较占内存,那有没有只数数就行的方法呢?显然是有的,那就是DP。

DP的思路可能比较难想到,主要就是问题的拆解。

首先可以肯定是dp[i, j]代表着从ij区间是否是回文串,那它的条件是什么呢?有如下几种情况。

  • ij相等

    相等的情况就不用多说了,就一个字母,显然是回文串

  • ij相邻

    相邻的情况也比较简单,只要这两个字母一样就可以,显然也是回文串

  • 其它

    其它的情况判断条件就略微复杂一点了,需要满足如下条件:

    • ij位置的字符串相同
    • dp[i + 1][j - 1]必须也是回文串
    • j必须比i更大

为什么j要比i大呢?这很简单,因为如果ij大的话这个值就没有存在的意义,显然不是一个回文串,因为我们根本找不到这样的一个区间字符串。

这样就只需要找到DP二维数组的左上部分即可,在处理的过程中进行可能性的累计,完成DP数组的处理后返回count即可。

自己的答案(排列组合)

排列组合的方法在解释中有说过,做过类似的回文串题目实现起来是比较简单的。

var countSubstrings = function(s) {
  const res = []
  const len = s.length
  for (let i = 0; i < len; i++) {
    getSubs(i, i)
    getSubs(i - 1, i)
  }
  function getSubs(left, right) {
    if (left < 0 || right >= len) return
    if (s.charAt(left) === s.charAt(right)) {
      res.push(s.slice(left, right + 1))
      getSubs(left - 1, right + 1)
    }
  }
  return res.length
};

递归函数中注意递归结束条件即可,避免造成无限递归。

更好的方法(DP)

DP需要注意的就是循环的方向了,因为需要依次修改ij的长度,并且只需要DP数组的右上部分,从应该从左到右进行循环取值操作,这样才能保证到dp[i][j]时,dp[i + 1][j - 1]已经被计算过了。

var countSubstrings = function(s) {
  let count = 0
  const len = s.length
  const dp = Array.from({length: len}, () => new Array(len).fill(false))
  for (let j = 0; j < len; j++) {
    for (let i = 0; i <= j; i++) {
      if (i === j) {
        count++
        dp[i][j] = true
      } else if (j - i === 1 && s[i] === s[j]) {
        count++
        dp[i][j] = true
      } else if (j - i > 1 && s[i] === s[j] && dp[i + 1][j - 1]) {
        count++
        dp[i][j] = true
      }
    }
  }
  return count
};

只需要考虑这三种情况即可,剩余情况就属于DP数组的左下部分了,无需操作,默认false即可。

更好的方法(DP+降维)

用DP嘛,大部分都可以有降维的操作,减少空间复杂度,这题也不例外,完全可以将二维数组变成一维数组,每次循环更新数组内容即可,进行一个纵向的压缩。

var countSubstrings = function(s) {
  let count = 0
  const len = s.length
  const dp = new Array(len).fill(false)
  for (let j = 0; j < len; j++) {
    for (let i = 0; i <= j; i++) {
      if (i === j) {
        count++
        dp[i] = true
      } else if (j - i === 1 && s[i] === s[j]) {
        count++
        dp[i] = true
      } else if (j - i > 1 && s[i] === s[j] && dp[i + 1]) {
        count++
        dp[i] = true
      } else {
        dp[i] = false
      }
    }
  }
  return count
};

这里相对于原始DP最大的变化就是多了else,为什么要多这个else呢?因为开始的时候说了,是用新的值替代旧的值,旧的值消失了,如果不用else做处理,那么取到的值可能还是就是的值,导致我们的计算出错。

其它就没啥了,这个DP降维还是比较简单的,代码基本上没什么变化,单纯的将DP数组从二维降成一维。



PS:想查看往期文章和题目可以点击下面的链接:

这里是按照日期分类的👇

前端刷题路-目录(日期分类)

经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇

前端刷题路-目录(题型分类)