这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战
回文子串(题号647)
题目
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
示例 1:
输入:"abc"
输出:3
解释:三个回文子串: "a", "b", "c"
示例 2:
输入:"aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"
提示:
输入的字符串长度不会超过 1000 。
链接
解释
这题啊,这题是经典排列组合。
看题意,最简单的办法就是排列组合了,求出所有的可能性,放到一个数组中,最后返回数组的长度即可。
记得之前有做过类似的题目,求某一个位置的回文串最大长度其实很简单——所有位置递归一次。
递归函数用来求从当前位置开始,向两侧扩散的最大回文串,但这个递归参数不能是简简单单的一个index
,而是left
和right
两个位置,为什么?因为可能是当前一个位置的值,也有可能是两个相同的连续字符串,需要考虑到这两种情况。
因为,每次位置需要进行两次递归操作,一次left
和right
相同,都是当前index
,一次right
是当前index
,left
是index - 1
。在处理完每个位置的可能性之后答案就出来了。
这种方法显然可以解决问题,但空间复杂度略高,比较占内存,那有没有只数数就行的方法呢?显然是有的,那就是DP。
DP的思路可能比较难想到,主要就是问题的拆解。
首先可以肯定是dp[i, j]
代表着从i
到j
区间是否是回文串,那它的条件是什么呢?有如下几种情况。
-
i
和j
相等相等的情况就不用多说了,就一个字母,显然是回文串
-
i
和j
相邻相邻的情况也比较简单,只要这两个字母一样就可以,显然也是回文串
-
其它
其它的情况判断条件就略微复杂一点了,需要满足如下条件:
i
和j
位置的字符串相同dp[i + 1][j - 1]
必须也是回文串j
必须比i
更大
为什么j
要比i
大呢?这很简单,因为如果i
比j
大的话这个值就没有存在的意义,显然不是一个回文串,因为我们根本找不到这样的一个区间字符串。
这样就只需要找到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需要注意的就是循环的方向了,因为需要依次修改i
和j
的长度,并且只需要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:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇