五分鐘帶你領略: 騰訊半年來出現最頻繁的算法之一——字符串解碼

簡要介紹

你們好,我是神三元。今天給你們分享一道有意思的算法題,在leetcode平臺上截圖以下: 前端

近半年來廣受各大公司的青睞,出現很是頻繁,在騰訊僅僅半年就出現了17次,若是說給滿分給5顆星的話,那麼這一題算得上實打實的五星題。算法

那到底是什麼讓這個算法從衆多的leetcode題庫中脫穎而出,在各個大廠中一而再、再而三地出現呢?編程

接下來,就讓咱們解開它的奧祕,領略這個算法到底多麼經典!數據結構

開局思路

剛開始拿到這道題,看到括號匹配問題,直覺上就想到了利用棧先進後出的性質,來完成先後字符串的拼接。可是後來嘗試了好久,發現問題並無那麼簡單,主要概括以下:編程語言

  1. 在中括號層次比較深的狀況下,如何完成回溯,將當前的字符與以前的字符拼接。
  2. 當出現次數並非個位數,而是相似100,1000 這樣的多位數,如何來解析。

接着,咱們利用不一樣的方法,來一步步來解決這兩個棘手的問題。spa

利用棧

說一下總體思路。掃描一遍字符串,針對不一樣的字符進行不一樣的處理:code

首先有兩個重要的變量,表示重複次數的 multi 值和累積字符串 res。cdn

    1. 遇到數字, 直接參加計算,累積multi值。
    1. 遇到普通字符(除[,]外),累積到 res 後面。
    1. 遇到 [, 將以前累積的字符串res壓棧,當前multi值壓到另外一個棧。而後當前 multi歸零,res置空。
    1. 遇到 ], 取出棧中multi值,將當前的 res 字符串重複 multi 次,賦值給臨時變量tmp,而後讓另外一個存放累積字符串的棧中彈出棧頂元素和當前的tmp拼接,做爲最新的累積字符串賦值給res。

若是如今沒看懂,沒有關係,給出代碼就明白了,讓你們直觀感覺一下:blog

var decodeString = function (s) {
  // 存放 【重複次數】 的棧
  let countStack = [];
  // 存放 【累積字符串】 的棧
  let resStack = [];
  // 用來累積的字符串 res
  let res = "";
  // 表示重複次數
  let multi = 0;
  for (let i = 0; i < s.length; i++) {
    let cur = s.charAt(i);
    if (cur == '[') {
      // 雙雙壓棧,保存了當前的狀態
      countStack.push(multi);
      resStack.push(res);
      // 紛紛置空,準備下面的累積
      multi = 0;
      res = "";
    } else if (cur == ']') {
      // 遇到 ],表示累積結束,要算帳了。
      // 【當前的串出現多少次】還保存在棧中,把它取出來
      let count = countStack.pop();
      let temp = "";
      // 讓 [ 和 ] 之間的字符串(就是累積字符串res)重複 count 次
      for(let i = 0; i < count; i++) {
        temp += res;
      }
      // 和前面已經求得的字符串進行拼接
      res = resStack.pop() + temp;
    } else if (cur >= '0' && cur <= '9') {
      // multi累積
      multi = multi * 10 + (cur - '0');
    } else {
      // 字符累積
      res += cur;    
    }
  }
  return res;
};
複製代碼

利用遞歸程序

遞歸的思路就容易一點,一旦遇到[,立馬進入新的遞歸程序,掃描到對應的]爲止。也就是說,凡是遇到括號,括號裏面的事情,所有交給子程序完成。建議你們看完代碼再來體會這句話:遞歸

var decodeString = function (s) {
  // 從第 0 個元素開始處理
  return dfs(s, 0);
};

let dfs = (s, n) => {
  let res = "";
  // 保存起始索引
  let i = n;
  // 同上,表示重複的次數
  let multi = 0;
  while(i < s.length) {
    let cur = s.charAt(i);
    // 遇到數字,累積 multi 值
    if(cur >= '0' && cur <= '9') 
      multi = multi * 10 + (cur - '0');
    else if(cur === '[') {
      // 劃重點!給子程序,把對應的 ] 索引和括號包裹的字符串返回
      // 即tmp 的格式爲 [索引,字符串]
      let tmp = dfs(s, i + 1);
      // 這樣下次遍歷就是從對應的 ] 後面遍歷了,由於當前已經把括號裏面的處理完了
      i = tmp[0];
      // 須要重複的字符串已經返回來了
      let repeatStr = tmp[1];
      for(let j = 0; j < multi; j++) {
        res += repeatStr;
      }
      // 當前已經把括號裏面的處理完,multi 置零,爲下一輪遍歷準備
      multi = 0;
    }else if(cur === ']') {
      // 遇到了對應的 ] ,返回 ] 索引和括號包裹的字符串
      return [i, res];
    } else {
      res += cur;
    }
    // 繼續遍歷
    i++;
  }
  return res;
}
複製代碼

兩種方法都順利經過。

估計作完這道題,仔細回味一下,也可以發現這道題的經典之處了:

  1. 考察對棧這種數據結構的理解
  2. 考察字符串的基本操做,涉及對編程語言的考察
  3. 考察對遞歸和回溯思想的理解。(其實字符串拼接就是向前回溯的過程)

作完這道題,是否是刷新了本身對於棧這種數據結構的認知呢?

它其實具備着自然的遞歸性質,只是咱們初學的時候,容易先入爲主地把這種先入後出的數據結構想的太簡單。固然它還有其餘神奇的功能,咱們放在下一期來分享。

❤️ 看完兩件事

若是你以爲這篇內容對你挺有啓發,我想邀請你幫我兩個小忙:

  1. 點贊,讓更多的人也能看到這篇內容(收藏不點贊,都是耍流氓 -_-)

  2. 關注公衆號「前端三元同窗」,每日堅持靈魂之問,碰見更好的本身!

相關文章
相關標籤/搜索