leetcode解題系列-最長迴文子串最全解法

前言

本人是一個一名前端菜🐔,正在努力加班加點學習中,看着大佬們寫的文章、demo啥的,羨慕不已。盼望着大佬們哪天能給個內推機會啥的那就nice了。 最近刷leetcode刷到這個題目,也在網上看到了各類各樣的解法,於溼乎我也嘗試着寫文章,記錄一下學習中值得分享的內容html

第一次寫文章,有不當之處還望各位大佬指出。前端

問題描述

  • 給定一個字符串 s,找到 s 中最長的迴文子串。你能夠假設 s 的最大長度爲 1000。  git

    示例 1:
    輸入: 「babad」
    輸出: 「bab」
    注意: 「aba」 也是一個有效答案。
    github

    示例 2:
    輸入: 「amnbvcxzzxcvbnmb」
    輸出: 「mnbvcxzzxcvbnm」
    算法

分析與求解

第一種 暴力解法

最容易想到的就是暴力解法,即外面的兩層循環找到全部子串,第三層循環判斷子串是不是迴文。方法的時間複雜度爲O(n^2),空間複雜度爲O(1)。編程

var longestPalindrome = function (s) {
  let n = s.length;
  if(n == 0) return ''; //字符串爲空則返回空
  if(n == 1) return s;  //字符串爲一個字符, 顯然返回自身
  let result = ''
  for (let i = 0; i < n; i++) { //字符串長度超過2
    for (let j = i + 1; j <= n; j++) {
      let str = s.slice(i, j); //可獲得全部子串
      let f = str.split('').reverse().join(''); //對字符串利用數組方法倒序

      if (str == f) { //判斷是否爲迴文
        result = str.length > result.length ? str : result;
      }
    }
  }
  return result;
}
console.log(longestPalindrome(str))

複製代碼

很顯然,此解法雖然最終可以獲得結果,可是效率很低,在這個講究高效編程的時代,這種方法是不可取的。 (此方法因爲時間複雜度過高,在leetcode上提交時會提示 Time Limit Exceeded,而且提交不了)c#

第二種 動態規劃

動態規劃(Dynamic Programming)是一種分階段求解決策問題的數學思想。總結起來就是一句話,大事化小,小事化了。數組

能採用動態規劃求解的問題的通常要具備3個性質:

1.最優化原理:若是問題的最優解所包含的子問題的解也是最優的,就稱該問題具備最優子結構,即知足最優化原理。
2.無後效性:即某階段狀態一旦肯定,就不受這個狀態之後決策的影響。也就是說,某狀態之後的過程不會影響之前的狀態,只與當前狀態有關。
3.有重疊子問題:即子問題之間是不獨立的,一個子問題在下一階段決策中可能被屢次使用到。(該性質並非動態規劃適用的必要條件,可是若是沒有這條性質,動態規劃算法同其餘算法相比就不具有優點bash

大概瞭解了一下動態規劃,下面讓咱們來看具體代碼學習

var longestPalindrome = function(s) {
    let len = s.length;
    let result;
    let i,j,L;
    let dp=Array(len).fill(0).map(x=>Array(len).fill(0));
    //console.log(dp);
    if(len<=1){
        return s
    }
    // 只有一個字符的狀況是迴文
    for(i = 0;i<len;i++){
        dp[i][i] = 1
        result = s[i]
    }

    // L是i和j之間的間隔數(由於間隔數從小到大漸增,因此大的間隔數總能包含小的間隔數)
    // i     j
    // abcdcba.length = L   因此 L = j-i+1; => j = i+L-1;
    for ( L = 2; L <= len; L++) {
        // 從0開始
        for ( i = 0; i <= len - L; i++) {
                j = i + L - 1;
            if(L == 2 && s[i] == s[j]) {
                dp[i][j] = 1;
                result = s.slice(i, i + L);
            }else if(s[i] == s[j] && dp[i + 1][j - 1] == 1) {
                dp[i][j] = 1
                result = s.slice(i, i + L);
            }

        }
    }
    //console.log(result);
    return result;
}

複製代碼

方法的時間複雜度爲O(n^2), 空間複雜度也爲O(n^2), 效率上整體來講相對暴力解法有很大的提高, 是一種不錯的解法, 並且動態規劃的應用場景不少, 想進一步學習的老鐵能夠點這裏動態規劃應用場景

第三種 Manacher算法

Manacher算法,又叫「馬拉車」算法,能夠在時間複雜度爲O(n)的狀況下求解一個字符串的最長迴文子串長度的問題。

在進行Manacher算法時,字符串都會進行上面的進入一個字符處理,好比輸入的字符爲acbbcbds,用「#」字符處理以後的新字符串就是#a#c#b#b#c#b#d#s#。

var str = 'ddabbade'
const longestPalindrome = function (s) {
  if (s.length == 1) {
    return s
  }
  let str = '#' + s.split('').join('#') + '#'
  let rl = []
  let mx = 0
  let pos = 0
  let ml = 0
  for (let i = 0; i < str.length; i++) {
    if (i < mx) {
      rl[i] = Math.min(rl[2 * pos - i], mx - i)
    } else {
      rl[i] = 1
    }
    while (i - rl[i] > 0 && i + rl[i] < str.length && str[i - rl[i]] == str[i + rl[i]]) {
      rl[i]++
    }
    if (rl[i] + i - 1 > mx) {
      mx = rl[i] + i - 1
      pos = i
    }
    if (ml < rl[i]) {
      ml = rl[i]
      sub = str.substring(i - rl[i] + 1, i + rl[i])
    }
  }
  return sub.split('#').join('').trim()
}
console.log(longestPalindrome(str)) //輸出dabbad
複製代碼

該方法的時間複雜度爲O(n),效率相對前兩種方法有巨大的提高。 有一篇大佬的文章有助於你們對Manacher算法的理解 Manacher算法詳解

總結

這三種方法是最長迴文子串的最經常使用解法。 暴力解法最容易理解也是最簡單,可是算法效率低下。 動態規劃對暴力解法作了必定的改進,它避免了在驗證迴文時進行沒必要要的重複計算。 而Manacher算法則是此題效率最高的算法,雖然相對前兩種方法稍微難理解一點,可是仔細看看也OK啦😄。 你們若是還有什麼更優的解法,歡迎評論區見😄 最後允許小生附上個人github地址 裏面記錄了我學習前端的點點滴滴,以爲有幫助的小哥哥小姐姐能夠給個小星星喲😄

相關文章
相關標籤/搜索