前端刷题路-Day52:最长回文子串(题号5)

758 阅读3分钟

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

最长回文子串(题号5)

题目

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

示例 1:

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

示例 2:

输入:s = "cbbd"
输出:"bb"

示例 3:

输入:s = "a"
输出:"a"

示例 4:

输入:s = "ac"
输出:"a"

提示:

  • 1 <= s.length <= 1000
  • s 仅由数字和英文字母(大写和/或小写)组成

链接

leetcode-cn.com/problems/lo…

解释

这题啊,这题是棋差一招。

其实差点就可以做出来的,可还是有一个点没想到,最后放弃了。

这题总共有3种解法,笔者想到的是第二种,第一种DP官方说的很详细,但笔者着实看不懂,真的看了很久,思路可以理解,但着代码有点令人迷惑。

第三种解法是数学公式,果断放弃,并不想挑战。

那剩下的就是第二种了,也是笔者一开始就想到了方法。

中心扩展方法,该方法其实很简单,就是从中间往两边找,判断左右是否相等,相等就继续扩展,不相等就GG。

这方法表面上看非常简单,重点就是要考虑中间可能是两个字母的情况,有时候中间是一个字母,有时候是两个字母,这时候就要进行处理了,笔者最开始的想法是判断两个字母的时候是在前还是在后,然后再进行处理啥的,逻辑有点绕,最后还是少考虑了情况导致GG。

其实这里压根就不应该放在一起进行比较,每循环到一个字母的时候,应该同时考虑中心点是一个字母还是两个字母的情况,取最大值作为当前位置的最长回文子串,这就完事了。

自己的答案

更好的方法(中心扩展法)

var longestPalindrome = function(s) {
  function expandString(left, right) {
    while (left >= 0 && right < s.length && s[left] === s[right]) {
      --left
      ++right
    }
    return right - left - 1
  }
  var start = 0
      end = 0
  for (let i = 0; i < s.length; i++) {
    var len1 = expandString(i, i)
        len2 = expandString(i, i + 1)
        len = Math.max(len1, len2)
    if (len > end - start) {
      start = i - ~~((len - 1) / 2)
      end = i + ~~(len / 2)
    }
  }
  return s.substring(start, end + 1)
};

代码比较简单,首先是一个expandString函数来进行扩展,注意这里的返回值,是right - left - 1,因为rightleft会多扩展一次,所以需要减一来获取正确的长度。

循环内部就是正常的逻辑,有len1len2两个长度,第一个很简答,就是从当前位置向两边扩展,len2就是从当前位置和下一个位置开始,假装中心点是两个开始扩展,由于expandString函数的内部逻辑,如果两个位置的字母是不一样的,直接GG,这个也不用担心,总之就是试一下的意思。

那么此时拿到两个长度了,取二者之间较大的那个,就是以当前位置为中心点点最大回文子串的长度了。

下面一步也比较关键,这一步会确定截取字符串的起始和结束位置。

其实是一个很巧妙的方法,笔者看了得有10分钟才理解这一步的精华。

这里给start赋值的时候,取的是~~((len - 1) / 2),为什么要这么做?首先考虑len是奇数的情况,如果是这种情况,其实没出处理的必要,因为~~代表着向下取整,1.5和1的结果一样的,不用考虑。

那如果len是偶数呢?这里的结果就凸显出来了,如果是偶数,减1之后,start会比end少退出一位,为什么要这么做?因为如果是偶数的话,意味着中心点是两个字母,但index是从第一个字母开始的,所以左侧的扩展需要比右侧少一个,这样扩展的个数就能对齐了。

说起来还是可能比较迷幻,笔者的表述能力也就到此为止了,尽力局。

如果还是看不懂可以打印点东西试试,考虑中心点是一个还是两个情况。

更好的方法(DP)

讲真,DP没看懂,但笔者还是拿JavaScript翻译了下官方答案,感兴趣的同学可以看看👇:

var longestPalindrome = function(s) {
  var len = s.length
  if (len < 2) return len
  var maxLen = 1
      begin = 0
      dp = Array.from({length: len}, () => new Array(len))
  for (let i = 0; i < len; i++) {
    dp[i][i] = true    
  }
  for (let L = 2; L <= len; L++) {
    for (let i = 0; i < len; i++) {
      var j = L + i - 1
      if (j >= len) break
      if (s[i] !== s[j]) {
        dp[i][j] = false
      } else {
        if (j - i < 3) {
          dp[i][j] = true
        } else {
          dp[i][j] = dp[i + 1][j - 1]
        }
      }
      if (dp[i][j] && j - i + 1 > maxLen) {
        maxLen = j - i + 1;
        begin = i
      }
    }    
  }
  return s.substring(begin, begin + maxLen)
};

更好的方法(Manacher)

这方法就是天书了,详见官方答案👉:这里



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

这里是按照日期分类的👇

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

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

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

有兴趣的也可以看看我的个人主页👇

Here is RZ