前端進階算法:常見算法題及完美題解

引言

瓶子君又來啦,她帶着前端算法來了👏👏👏前端

大廠面試愈來愈難,對算法的要求也愈來愈多,當面試官問到一個算法題,給出一份完美答案能大大提升面試官的好感度,本系列就是致力於打造一套適用於前端的算法。git

往期精彩系列文章:github

三篇交流羣刷題總結:面試

題目(題目僅僅會在「前端進階算法集訓營」裏發佈,每早 9: 00):瀏覽器

數組篇:緩存

鏈表篇:markdown

字符串篇:

棧篇:

本節是第四周的總結與回顧,下面開始進入正題吧!👇👇👇

1、百度:實現一個函數,判斷輸入是否是迴文字符串

1. 解法一:使用API

function isPlalindrome(input) {
  if (typeof input !== 'string') return false;
  return input.split('').reverse().join('') === input;
}
複製代碼

2. 解法二:不使用API

function isPlalindrome(input) {
  if (typeof input !== 'string') return false;
  let i = 0, j = input.length - 1
  while(i < j) {
      if(input.charAt(i) !== input.charAt(j)) return false
      i ++
      j --
  }
  return true
}
複製代碼

3. 更多題解

詳見 百度:實現一個函數,判斷輸入是否是迴文字符串

2、字節&Leetcode3:無重複字符的最長子串

1. 題目

給定一個字符串,請你找出其中不含有重複字符的 最長子串 的長度。

示例 1:

輸入: "abcabcbb"
輸出: 3 
解釋: 由於無重複字符的最長子串是 "abc",因此其長度爲 3複製代碼

示例 2:

輸入: "bbbbb"
輸出: 1
解釋: 由於無重複字符的最長子串是 "b",因此其長度爲 1複製代碼

示例 3:

輸入: "pwwkew"
輸出: 3
解釋: 由於無重複字符的最長子串是 "wke",因此其長度爲 3。
     請注意,你的答案必須是 子串 的長度,"pwke" 是一個子序列,不是子串。
複製代碼

2. 解法

解法一:維護數組

解題思路: 使用一個數組來維護滑動窗口

遍歷字符串,判斷字符是否在滑動窗口數組裏

  • 不在則 push 進數組
  • 在則刪除滑動窗口數組裏相同字符及相同字符前的字符,而後將當前字符 push 進數組
  • 而後將 max 更新爲當前最長子串的長度

遍歷完,返回 max 便可

畫圖幫助理解一下:

代碼實現:

var lengthOfLongestSubstring = function(s) {
    let arr = [], max = 0
    for(let i = 0; i < s.length; i++) {
        let index = arr.indexOf(s[i])
        if(index !== -1) {
            arr.splice(0, index+1);
        }
        arr.push(s.charAt(i))
        max = Math.max(arr.length, max) 
    }
    return max
};
複製代碼

時間複雜度:O(n2), 其中 arr.indexOf() 時間複雜度爲 O(n) ,arr.splice(0, index+1) 的時間複雜度也爲 O(n)

空間複雜度:O(n)

解法二:維護下標

解題思路: 使用下標來維護滑動窗口

代碼實現:

var lengthOfLongestSubstring = function(s) {
    let index = 0, max = 0
    for(let i = 0, j = 0; j < s.length; j++) {
        index = s.substring(i, j).indexOf(s[j]) 
        if(index !== -1) { 
            i = i + index + 1 
        } 
        max = Math.max(max, j - i + 1) 
    }
    return max
};
複製代碼

時間複雜度:O(n2)

空間複雜度:O(n)

解法三:優化的Map

解題思路:

使用 map 來存儲當前已經遍歷過的字符,key 爲字符,value 爲下標

使用 i 來標記無重複子串開始下標,j 爲當前遍歷字符下標

遍歷字符串,判斷當前字符是否已經在 map 中存在,存在則更新無重複子串開始下標 i 爲相同字符的下一位置,此時從 ij 爲最新的無重複子串,更新 max ,將當前字符與下標放入 map

最後,返回 max 便可

代碼實現:

var lengthOfLongestSubstring = function(s) {
    let map = new Map(), max = 0
    for(let i = 0, j = 0; j < s.length; j++) {
        if(map.has(s[j])) {
            i = Math.max(map.get(s[j]) + 1, i)
        }
        max = Math.max(max, j - i + 1)
        map.set(s[j], j)
    }
    return max
};
複製代碼

時間複雜度:O(n)

空間複雜度:O(n)

3. 更多題解

詳見 字節&Leetcode3:無重複字符的最長子串

3、文章:全方位解讀棧結構

1. 數據結構棧

棧是一種聽從後進先出 (LIFO / Last In First Out) 原則的有序集合,它的結構相似以下:

代碼實現

function Stack() {
  let items = []
  this.push = function(e) { 
    items.push(e) 
  }
  this.pop = function() { 
    return items.pop() 
  }
  this.isEmpty = function() { 
    return items.length === 0 
  }
  this.size = function() { 
    return items.length 
  }
  this.clear = function() { 
    items = [] 
  }
}
複製代碼

查找:從棧頭開始查找,時間複雜度爲 O(n)

插入或刪除:進棧與出棧的時間複雜度爲 O(1)

2. 面試:調用棧

調用棧是 JavaScript 用來管理函數執行上下文的一種數據結構,它記錄了當前函數執行的位置,哪一個函數正在被執行。 若是咱們執行一個函數,就會爲函數建立執行上下文並放入棧頂。 若是咱們從函數返回,就將它的執行上下文從棧頂彈出。 也能夠說調用棧是用來管理這種執行上下文的棧,或稱執行上下文棧(執行棧)。

3. 面試:棧空間與堆空間

JavaScript 中的內存空間主要分爲三種類型:

  • 代碼空間:主要用來存放可執行代碼
  • 棧空間:調用棧的存儲空間就是棧空間。
  • 堆空間

代碼空間主要用來存放可執行代碼的。棧空間及堆空間主要用來存放數據的。接下來咱們主要介紹棧空間及堆空間。

當調用棧中執行完成一個執行上下文時,須要進行垃圾回收該上下文以及相關數據空間,存放在棧空間上的數據經過 ESP 指針來回收,存放在堆空間的數據經過副垃圾回收器(新生代)與主垃圾回收器(老生代)來回收。

4. 詳情

詳細請看 前端進階算法5:全方位解讀前端用到的棧結構(+leetcode刷題)

4、字節&leetcode155:最小棧(包含getMin函數的棧)

1. 題目

設計一個支持 pushpoptop 操做,並能在常數時間內檢索到最小元素的棧。

  • push(x) —— 將元素 x 推入棧中。
  • pop() —— 刪除棧頂的元素。
  • top() —— 獲取棧頂元素。
  • getMin() —— 檢索棧中的最小元素。

示例:

MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.
複製代碼

2. 解法

在常數時間內檢索到最小元素的棧,即僅需保證 getMin 的時間複雜度爲 O(1) 便可

var MinStack = function() {
    this.items = []
    this.min = null
};

// 進棧
MinStack.prototype.push = function(x) {
    if(!this.items.length) this.min = x 
    this.min = Math.min(x, this.min)
    this.items.push(x) 
};

// 出棧
MinStack.prototype.pop = function() {
    let num = this.items.pop() 
    this.min = Math.min(...this.items)
    return num
};

// 獲取棧頂元素
MinStack.prototype.top = function() {
    if(!this.items.length) return null
    return this.items[this.items.length -1] 
};

// 檢索棧中的最小元素
MinStack.prototype.getMin = function() {
    return this.min
};
複製代碼

時間複雜度:進棧O(1),出棧O(n),獲取棧頂元素O(1),獲取最小元素O(1)

空間複雜度:O(n)

3. 更多題解

詳見 字節&leetcode155:最小棧(包含getMin函數的棧)

5、圖解騰訊&leetcode20:有效的括號

1. 題目

給定一個只包括 '('')''{''}''['']' 的字符串,判斷字符串是否有效。

有效字符串需知足:

  • 左括號必須用相同類型的右括號閉合。
  • 左括號必須以正確的順序閉合。

注意空字符串可被認爲是有效字符串。

示例 1:

輸入: "()"
輸出: true
複製代碼

示例 2:

輸入: "()[]{}"
輸出: true
複製代碼

示例 3:

輸入: "(]"
輸出: false
複製代碼

示例 4:

輸入: "([)]"
輸出: false
複製代碼

示例 5:

輸入: "{[]}"
輸出: true
複製代碼

2. 解法:利用棧結構

解題思路: 將字符串中的字符依次入棧,遍歷字符依次判斷:

  • 首先判斷該元素是不是 {([ ,直接入棧
  • 不然該字符爲 })] 中的一種,若是該字符串有效,則該元素應該與棧頂匹配,例如棧中元素有 ({, 若是繼續遍歷到的元素爲 ), 那麼當前元素序列爲 ({) 是不可能有效的,因此此時與棧頂元素匹配失敗,則直接返回 false ,字符串無效

當遍歷完成時,全部已匹配的字符都已匹配出棧,若是此時棧爲空,則字符串有效,若是棧不爲空,說明字符串中還有未匹配的字符,字符串無效

畫圖幫助理解一下:

代碼實現:

var isValid = function(s) {
    let map = {
        '{': '}',
        '(': ')',
        '[': ']'
    }
    let stack = []
    for(let i = 0; i < s.length ; i++) {
        if(map[s[i]]) {
            stack.push(s[i])
        } else if(s[i] !== map[stack.pop()]){
            return false
        }
    }
    return stack.length === 0
};
複製代碼

時間複雜度:O(n)

空間複雜度:O(n)

3. 更多題解

詳見 圖解騰訊&leetcode20:有效的括號

6、leetcode1047:刪除字符串中的全部相鄰重複項

1. 題目

給出由小寫字母組成的字符串 S重複項刪除操做 會選擇兩個相鄰且相同的字母,並刪除它們。

在 S 上反覆執行重複項刪除操做,直到沒法繼續刪除。

在完成全部重複項刪除操做後返回最終的字符串。答案保證惟一。

示例:

輸入:"abbaca"
輸出:"ca"
解釋:
例如,在 "abbaca" 中,咱們能夠刪除 "bb" 因爲兩字母相鄰且相同,這是此時惟一能夠執行刪除操做的重複項。以後咱們獲得字符串 "aaca",其中又只有 "aa" 能夠執行重複項刪除操做,因此最後的字符串爲 "ca"複製代碼

提示:

  1. <= S.length <= 20000
  2. S 僅由小寫英文字母組成。

2. 解法:利用棧

解題思路: 遍歷字符串,依次入棧,入棧時判斷與棧頭元素是否一致,若是一致,即這兩個元素相同相鄰,則須要將棧頭元素出棧,而且當前元素也無需入棧

解題步驟: 遍歷字符串,取出棧頭字符,判斷當前字符與棧頭字符是否一致

  • 不一致,棧頭字符進棧,當前字符進棧
  • 一致,即棧頭字符與當前字符相同相鄰,都不須要進棧,直接進入下次遍歷便可

遍歷完成後,返回棧中字符串

代碼實現:

var removeDuplicates = function(S) {
    let stack = []
    for(c of S) {
        let prev = stack.pop()
        if(prev !== c) {
            stack.push(prev)
            stack.push(c)
        }
    }
    return stack.join('')
};
複製代碼

時間複雜度:O(n)

空間複雜度:O(n)

3. 更多題解

詳見 leetcode1047:刪除字符串中的全部相鄰重複項

7、前端算法集訓營第一期免費加入啦

從0到1構建完整的數據結構與算法體系!

在這裏,瓶子君不只介紹算法,還將算法與前端各個領域進行結合,包括瀏覽器、HTTP、V八、React、Vue源碼等。

在這裏,你能夠天天學習一道大廠算法題(阿里、騰訊、百度、字節等等)或 leetcode,瓶子君都會在次日解答喲!

掃碼加入,若羣人數已達上線,關注公衆號「前端瓶子君」,回覆「算法」便可自動加入

⬆️ 掃碼關注公衆號「前端瓶子君」,回覆「算法」便可自動加入 👍👍👍

相關文章
相關標籤/搜索