这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战
这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。
158. 回文子串 (palindromic-substrings)
标签
- 动态规划
- 中等
题目
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
示例 1
输入:"abc"
输出:3
解释:三个回文子串: "a", "b", "c"
示例 2
输入:"aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"
基本思路
暴力解
最暴力的,遍历考察所有子串,判断是否是回文串,是就 + 1。时间复杂度 O(n^3)
这个没啥好说的,直接看代码吧非常清晰了。
const isPalindrome = (s) => {
return s.split('').reverse().join('') === s
}
var countSubstrings = function(s) {
let count = 0, len = s.length, curSubStr = ''
// 遍历所有子串,两指针标记子串两头
for (let i = 0; i < len; i++) {
// 注意 j 从 i 开始
for (let j = i; j < len; j++) {
// 本轮判断的子串
curSubStr = s.substring(i, j+1)
// 如果是回文就 + 1
if (isPalindrome(curSubStr)) {
count++
}
}
}
return count
};
console.log(countSubstrings('aaa'))
动态规划
其实大部分的字符串问题,动态规划的解法都适用。
从动态规划这篇我们了解到动态规划的基本步骤是下面三步:
- 寻找最优子结构(状态表示)
- 归纳状态转移方程(状态计算)
- 边界初始化
我们还是根据之前的基本步骤来
- 状态表示:
我们要判断所有子串的回文情况,那么要用一种方式表示所有子串
这个问题我们想到以 [i, j] 来做子串的分割, 那么dp[i][j] = true
可以表示 s[i..j]
是回文子串, dp[i][j] = false
可以表示 s[i..j]
不是回文子串, 然后这个二维数组中 true
的数量就是 count
- 状态转移方程:
如何从历史状态转到当前呢,回文我们想到其实跟历史 dp[i + 1][j - 1] 的状态是相关的,可转换的, 看下面例子
i+1 j-1
| |
asvkkkvsa
| |
i j
历史 dp[i+1][j-1]
是表示 vkkkv
这个子串是回文的 那么 dp[i+1][j-1] === true
,
这时候往外扩变成 dp[i][j] 如果 s[i] === s[j]
就像现在这样 svkkkvs
都是 s
,那么转换后还是回文 dp[i][j] = true
- 边界初始化:
- 首先
i <= j
,i 做开头,j 做结尾,否则就重复了 单个字符
时 或者说i === j
时,必然是回文2个字符
时 或者说j-i=1
下标相减等于1 时,如果再满足s[i]===s[j]
也必然是回文 如aa
,bb
等
const countSubstrings = (s) => {
let count = 0, len = s.length
// 建立 dp
let dp = new Array(len).fill(0).map(() => new Array(len).fill(false))
for (let j = 0; j < len; j++) {
for (let i = 0; i <= j; i++) {
// 三种情况下可判断是回文 count++, dp[i][j] = true
if (i === j ||
j - i === 1 && s[i] === s[j] ||
j - i > 1 && s[i] == s[j] && dp[i + 1][j - 1]
) {
dp[i][j] = true
count++
}
}
}
return count;
};
console.log(countSubstrings('aaa'))
降维优化
一般动态规划的优化,就想到降维,原来需要二维的降到一维。画图就可以看的更清楚,其实就是原地修改数组,只保留最新的某个维度的状态。
直接看代码了, 思路几乎和上面一模一样
const countSubstrings = (s) => {
let count = 0, len = s.length;
const dp = new Array(len).fill(false);
for (let j = 0; j < len; j++) {
for (let i = 0; i <= j; i++) {
// j - i 为 0 或 1 且 s[i] === s[j] 满足条件
if ( j - i <= 1 && s[i] === s[j] ||
s[i] === s[j] && dp[i + 1]
) {
dp[i] = true;
count++;
}
}
}
return count;
};
console.log(countSubstrings('aaa'))
另外向大家着重推荐下这个系列的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列
今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 点击此处交个朋友
Or 搜索我的微信号infinity_9368
,可以聊天说地
加我暗号 "天王盖地虎" 下一句的英文
,验证消息请发给我
presious tower shock the rever monster
,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧