前端算法必刷题系列[86]

355 阅读3分钟

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

这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。

158. 回文子串 (palindromic-substrings)

标签

  • 动态规划
  • 中等

题目

leetcode 传送门

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

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

示例 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'))

动态规划

其实大部分的字符串问题,动态规划的解法都适用。

动态规划这篇我们了解到动态规划的基本步骤是下面三步:

  1. 寻找最优子结构(状态表示)
  2. 归纳状态转移方程(状态计算)
  3. 边界初始化

我们还是根据之前的基本步骤来

  1. 状态表示:

我们要判断所有子串的回文情况,那么要用一种方式表示所有子串

这个问题我们想到以 [i, j] 来做子串的分割, 那么dp[i][j] = true可以表示 s[i..j] 是回文子串, dp[i][j] = false可以表示 s[i..j] 不是回文子串, 然后这个二维数组中 true 的数量就是 count

  1. 状态转移方程:

如何从历史状态转到当前呢,回文我们想到其实跟历史 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

  1. 边界初始化:
  • 首先 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,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧

参考