LeetCode 5. 最长回文子串 | 算法-从菜鸟开始

简介: 本文是《算法-从菜鸟开始》系列文章的第6篇,欢迎收藏、留言、点赞。话不多说,让我们继续我们的算法之旅。

一、LeetCode 5. 最长回文子串


题目介绍:


给你一个字符串 s,找到 s 中最长的回文子串。


示例:


输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。


基本思考:什么是回文字符串?


首先在处理这个问题的时候,我们要先考虑什么样的字符串时回文字符串,总结一下特点:


回文字符串长度 示例 特点
1 "a" 一个字符肯定是,不用猜
2 "aa" 两个字符一模一样,肯定是
> 2 "abcba" 从中心点看,左右对称


现在我们假定某个字符串s是回文字符串,该字符串的长度可以使用从索引i到索引j来表示(即从索引i开始到索引j结束的一个字符串)。


/**
 * @method isPalindrome
 * @description 判断字符串s的索引i到索引j是否是回文字符串
 * @param {number} i
 * @param {number} j
 */
const isPalindrome = (i, j) => {
  // 1. 是否是一个字符
  if (i === j) return true;
  // 2. 是否是首尾一致,首尾不一致肯定不是回文字符串
  if (s[i] !== s[j]) return false;
  // 3. 判断索引i和索引j的关系,
  //    基于上面的2条件,走到条件3说明 s[i]和s[j]中间就只有一个字符了,所以这就是一个回文字符串
  if (i + 1 === j) return true;
  // 如果以上条件都不满足,则继续向中间逼近,递归处理
  return isPalindrome(i + 1, j - 1);
}


使用递归的方式来实现,算法的时间复杂度是 O(n)O(n)O(n)


接下来我们思考,如何查询出字符串s中的最长回文子串呢?


老实人的想法:暴力枚举


老实人再次上线,可以枚举出字符串s中所有的子串组合,取最大值即可。


上代码!


/**
 * @param {string} s
 * @return {string}
 */
var longestPalindrome = function(s) {
  /**
   * @param {number} i
   * @param {number} j
   */
  const isPalindrome = (i, j) => {
    // 1. 是否是一个字符
    if (i === j) return true;
    // 2. 是否是首尾一致,首尾不一致肯定不是回文字符串
    if (s[i] !== s[j]) return false;
    // 3. 判断索引i和索引j的关系,
    //    基于上面的2条件,走到条件3说明 s[i]和s[j]中间就只有一个字符了,所以这就是一个回文字符串
    if (i + 1 === j) return true;
    // 如果以上条件都不满足,则继续向中间逼近,递归处理
    return isPalindrome(i + 1, j - 1);
  }
  // 缓存字符串s的长度,不必在执行循环时,每次都计算一次。
  const len = s.length;
  // 设置起始索引indexI
  let indexI = 0;
  // 设置结束索引为indexJ
  let indexJ = 0;
  // 两层循环,将每一种子串都枚举
  for (let i = 0; i < len; i++) {
    for (let j = i + 1; j < len; j++) {
      // 判断是否是回文字符串
      if (isPalindrome(i, j)) {
        // 如果是回文子串,则将当前长度与历史长度进行比较,取二者的最大值
        if (j - i > indexJ - indexI) {
          // 记录此时的i、j
          indexI = i;
          indexJ = j;
        }
      }
    }
  }
  // 注意审题,返回的是回文子串,不是长度
  return s.substring(indexI, indexJ + 1);
};


提交代码!


网络异常,图片无法展示
|


执行结果是:超出时间限制,看起来似乎有问题,也好像没啥问题,实际就是算法效率不高!


再次看下这个算法实现时的时间复杂度是多少?两层for循环是O(n2)O(n^2)O(n2),在加上最里面判断是否是回文字符串的isPalindrome方法O(n)O(n)O(n),最终的复杂度是 O(n3)O(n^3)O(n3)


递归优化经典策略 - 缓存优化


每一个递归的实现都是可以进行缓存优化的,在函数isPalindrome中,我们可以使用缓存cache[i][j]来记录对应ij位置的结果,不必每次都重新计算了。


小小调整下:


// 定义二维数组,缓存结果
const cache = [];
const isPalindrome = (i, j) => {
  // 如果不存在某个一维数组则进行初始化
  if (cache[i] === undefined) {
    cache[i] = [];
  }
  // 如果对应[i][j]存在,则直接返回结果
  if (cache[i][j] !== undefined) {
    return cache[i][j];
  }
  // 1. 是否是一个字符
  if (i === j) return true;
  // 2. 是否是首尾一致,首尾不一致肯定不是回文字符串
  if (s[i] !== s[j]) return false;
  // 3. 判断索引i和索引j的关系,
  //    基于上面的2条件,走到条件3说明 s[i]和s[j]中间就只有一个字符了,所以这就是一个回文字符串
  if (i + 1 === j) return true;
  // 如果以上条件都不满足,则继续向中间逼近,递归处理
  cache[i][j] = isPalindrome(i + 1, j - 1);
  return cache[i][j];
}


再次提交,看看


网络异常,图片无法展示
|


执行结果是通过了,但是时间复杂度还是O(n3)O(n^3)O(n3)性能上还是非常不好看,接下来我们可以尝试换一种角度来看看这个问题。


动态规划看问题


我们假定在索引i和索引j之间是回文字符串,j在前,i在后,使用dp[i][j]来表示。那么dp[i][j]的值相当于中间的回文字符串dp[i - 1][j + 1]结果加上边界值s[i] === s[j]的结果。


看下代码的实现


/**
 * @param {string} s
 * @return {string}
 */
var longestPalindrome = function(s) {
  const len = s.length;
  const dp = [];
  let [ indexJ, indexI ] = [0, 0];
  for (let i = 0; i < len; i++) {
    if (!dp[i]) {
      dp[i] = []
    }
    dp[i][i] = true;
    // 注意是从j到i,so...
    for (j = 0; j < i; j++) {
      // 在这里要注意dp[i - 1][j + 1]的边界问题
      // 如 cbb,当i=2,j=1时,dp[i - 1][j + 1]的值是dp[1][2]是不存在的 undefined,但是bb确实符合回文字符串
      // 所以要确定 dp[i - 1][j + 1]是否是已经存在的值
      dp[i][j] = s[i] === s[j] ? dp[i - 1]?.[j + 1] ?? true  : false;
      // 判断是否是符合条件,并且长度大于历史最大值
      if (dp[i][j] && (i - j) > (indexI - indexJ)) {
        [indexJ, indexI] = [j, i]
      }
    }
  } 
  return s.substring(indexJ, indexI + 1)
};


这里的时间复杂度已经是O(n2)O(n^2)O(n2)了呦


算法-从菜鸟开始,而无止境。与君共勉!


相关文章
|
2月前
Leetcode第五题(最长回文子串)
这篇文章介绍了解决LeetCode第五题“最长回文子串”问题的一种方法,使用了中心扩展法来寻找给定字符串中的最长回文子串。
36 0
|
2月前
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
41 0
|
29天前
|
存储 算法 Java
leetcode算法题-有效的括号(简单)
【11月更文挑战第5天】本文介绍了 LeetCode 上“有效的括号”这道题的解法。题目要求判断一个只包含括号字符的字符串是否有效。有效字符串需满足左括号必须用相同类型的右括号闭合,并且左括号必须以正确的顺序闭合。解题思路是使用栈数据结构,遍历字符串时将左括号压入栈中,遇到右括号时检查栈顶元素是否匹配。最后根据栈是否为空来判断字符串中的括号是否有效。示例代码包括 Python 和 Java 版本。
|
2月前
|
算法
每日一道算法题(Leetcode 20)
每日一道算法题(Leetcode 20)
29 2
|
4月前
|
算法
测试工程师的技能升级:LeetCode算法挑战与职业成长
这篇文章通过作者亲身体验LeetCode算法题的过程,探讨了测试工程师学习算法的重要性,并强调了算法技能对于测试职业成长的必要性。
76 1
测试工程师的技能升级:LeetCode算法挑战与职业成长
|
4月前
|
算法
LeetCode第5题最长回文子串
该文章介绍了 LeetCode 第 5 题最长回文子串的解法,通过分析回文子串的特点,使用动态规划的思想,用二维数组记录字符串是否为回文串,从后往前遍历找子串,减少重复判断,最终找到最长回文子串,并总结了解题时通过举例推导找规律的思路。
LeetCode第5题最长回文子串
|
4月前
|
算法 Java
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
54 6
|
4月前
|
存储 算法 Java
LeetCode经典算法题:打家劫舍java详解
LeetCode经典算法题:打家劫舍java详解
70 2
|
4月前
|
人工智能 算法 Java
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
52 1
|
4月前
|
存储 算法 Java
LeetCode经典算法题:预测赢家+香槟塔java解法
LeetCode经典算法题:预测赢家+香槟塔java解法
62 1