每週一練 之 數據結構與算法(Stack)

最近公司內部在開始做前端技術的技術分享,每週一個主題的 每週一練,以基礎知識爲主,感覺挺棒的,跟着團隊的大佬們學習和複習一些知識,新人也可以多學習一些知識,也把團隊內部學習氛圍營造起來。

我接下來會開始把每週一練的題目和知識整理一下,便於思考和鞏固,就像今天這篇開始。

學習的道路,很漫長,要堅持,希望大家都能掌握自己喜歡的技術,和自己需要的技術。

歡迎查看我的 個人主頁 && 個人博客 && 個人知識庫 && 微信公衆號“前端自習課”

本週練習內容:數據結構與算法 —— Stack

這些都是數據結構與算法,一部分方法是團隊其他成員實現的,一部分我自己做的,有什麼其他實現方法或錯誤,歡迎各位大佬指點,感謝。

一、棧有什麼特點,生活中有什麼例子?

  • 棧( stack )又稱堆棧,是一種後進先出的有序集合,其中一端爲棧頂,另一端爲棧底,添加元素(稱爲壓棧/入棧或進棧)時,將新元素壓入棧頂,刪除元素(稱爲出棧或退棧)時,將棧底元素刪除並返回被刪除元素。
  • 特點:先進後出,後進先出
  • 例子:一疊書、一疊盤子。

棧

二、實現一個棧,並實現下面方法

  • push(element):添加一個新元素到棧頂。
  • pop():移除棧頂的元素,同時返回被移除的元素。
  • peek():返回棧頂的元素,不對棧做任何修改 (這個方法不會移除棧頂的元素,僅僅返回它)。
  • isEmpty():如果棧沒有任何元素就返回 true,否則返回 false
  • clear():移除棧裏面的所有元素。
  • size():返回棧裏的元素個數。這個方法與數組的 length 屬性類似。

方法1:ES6實現

class Stack {
    constructor (){
        this.items = []
    }
    push( element ){
        this.items.push(element)
    }
    pop(){
        return this.items.pop()
    }
    peek(){
        return this.items[this.items.length - 1]
    }
    isEmpty(){
        return this.items.length === 0
    }
    clear(){
        this.items = []
    }
    size(){
        return this.items.length
    }
}

上面實現的方式雖然簡單,但是內部 items 屬性是公共的,爲了滿足面向對象變成私有性的原則,我們應該讓 items 作爲私有屬性,因此我們可以使用 ES6 中 SymbolWeakMap 來實現:

方法2:使用 ES6 的 Symbol 基本數據類型實現
知識點複習:ES6 中的 Symbol 介紹

const _items = Symbol()
class Stack {
    constructor (){
        this[_items] = []
    }
    push (element){
        this[_items].push(element)
    }
    // 剩下方法和第一種實現的差不多,這裏省略
    // 只要把前面方法中的 this.items 更改爲 this[_items]
}

方法3:使用 ES6 的 WeakMap 實現
知識點複習:ES6 中的 WeakMap 介紹

const items = new WeakMap()
class Stack {
    constructor (){
        items.set(this, [])
    }
    push (element){
        let item = items.get(this)
        item.push(element)
    }
    // 剩下方法和第一種實現的差不多,這裏省略
    // 只要把前面方法中的獲取 this.items 的方式,更改爲 items.get(this) 獲取
}

三、編寫一個函數,實現十進制轉二進制

題目意思很簡單,就是十進制轉二進制,但是在實際工作開發中,我們更願意實現的是任意進制轉任意進制,不過呢,我們還是以解決問題爲首要目標呀。

當然,業務需求可以直接使用 toString(2) 方法,但是爲了練習,咱還是不這麼用咯。

方法1:使用前面定義的 Stack 類
這裏使用前面題目中定義的 Stack 類。

/**
 * 十進制轉換爲二進制
 * @param {Number} bit 
 */
function bitset (bit){
    if(bit == 0) return '0'
    if(!/^[0-9]+.?[0-9]*$/.test(bit)){
        return new Error('請輸入正確的數值!')
    }

    let stack = new Stack(), result = ''
    while (bit > 0){
        stack.push(bit % 2)
        bit = Math.floor(bit / 2)
    }
    while (!stack.isEmpty()){
        result += stack.pop().toString()
    }
    return result

}

方法2:簡單實現
下面這個方法,其實不太好,因爲沒有怎麼用到這次要練習的方法,哈哈。

/**
 * 十進制轉換爲二進制
 * @param {Number} bit 
 */
function bitset (bit){
    if(bit == 0) return '0'
    if(!/^[0-9]+.?[0-9]*$/.test(bit)){
        return new Error('請輸入正確的數值!')
    }

    let arr = []
    while(bit > 0){
        arr.push(bit % 2)
        bit = Math.floor(bit / 2)
    }
    return arr.reverse().join('')
}

另外可以參考:wikiHow - 從十進制轉換爲二進制

四、編寫一個函數,實現檢驗圓括號順序的有效性

主要目的就是:該函數接收一個圓括號字符串,判斷裏面的括號順序是否有效,如果有效則返回 true 反之 false
如:

  • ( -> false
  • () -> true
  • (() -> false
  • ()) -> false
  • ()) -> false
  • (((()()))()) -> true

這個題目實現的主要方法是:遍歷字符串,先排除錯誤情況,然後將 ( 入棧保存,將 ) 入棧匹配前一個元素是否是 ( ,如果是,則 pop() 前一個元素 (,如果不是,則 push() 這個 ) 入棧,最終查看棧是否爲空,若是則檢驗成功,否則失敗。

方法1:使用前面定義的 Stack 類
這裏使用前面題目中定義的 Stack 類。

/**
 * 檢驗圓括號順序的有效性
 * @param {String} str 
 */
function validParentheses (str){
    if(!str || str.length === 0 || str[0] === ')') return false

    let stack = new Stack()
    str.split('').forEach(char => {
        let status = stack.peek() === '(' && char === ')'
        status ? stack.pop() : stack.push(char)
    })
    return stack.isEmpty()
}

方法2:出入棧操作

/**
 * 檢驗圓括號順序的有效性
 * @param {String} str 
 */
function validParentheses (str){
    if(!str || str.length === 0 || str[0] === ')') return false

    let arr = []
    for(let i = 0; i < str.length ; i++){
        str[i] === '(' ? arr.push(str[i]) : arr.pop()
    }
    return arr.length === 0
}

五、改造題二,添加一個 min 函數來獲得棧中最小元素

步驟 數據棧 輔助棧 最小值
1.push 3 3 0 3
2.push 4 3, 4 0, 0 3
3.push 2 3, 4, 2 0, 0, 2 2
4.push 1 3, 4, 2 ,1 0, 0, 2, 3 1
5.pop 3, 4, 2 0, 0, 2 2
6.pop 3, 4 0, 0 3
7.push 3, 4 ,0 0, 0, 2 0

使用示例如下:

let stack = new Stack();
stack.push(3);
console.log('After push 3, Min item is', stack.min());
stack.push(4);
console.log('After push 4, Min item is', stack.min());
stack.push(2);
console.log('After push 2, Min item is', stack.min());
stack.push(1);
console.log('After push 1, Min item is', stack.min());
stack.pop();
console.log('After pop, Min item is', stack.min());
stack.pop();
console.log('After pop, Min item is', stack.min());
stack.push(0);
console.log('After push 0, Min item is', stack.min());

提示:利用輔助棧(Web 端可利用數組),每次對棧 push/pop 元素時,也同時更新輔助棧(存儲最小元素的位置)

方法1:小操作

class Stack {
  constructor() {
    this.items = [];
    this.minIndexStack = [];
  }

  push(element) {
    this.items.push(element);
    let minLen = this.minIndexStack.length;
    let minItemIndex = this.minIndexStack[minLen - 1];
    if(minLen === 0 || this.items[minItemIndex] > item) {
      this.minIndexStack.push(this.items.length - 1);
    } else {
      this.minIndexStack.push(minItemIndex);
    }
  }

  pop() {
    this.minIndexStack.pop();
    return this.items.pop();
  }
  
  min() {
    let len = this.minIndexStack.length;
    return (len > 0 && this.items[this.minIndexStack[len - 1]]) || 0;
  }

  peek() {
    return this.items[this.items.length - 1];
  }
  
  // 省略其它方法
}

方法2:與方法1中push實現的差異

class Stack {
    constructor (){
        this.items = [] // 數據棧
        this.arr = []   // 輔助棧
    }
    push( element ){
        this.items.push(element)
        let min = Math.min(...this.items)
        this.arr.push( min === element ? this.size() - 1 : 0)
    }
    pop(){
        this.arr.pop()
        return this.items.pop()
    }
    peek(){
        return this.items[this.items.length - 1]
    }
    isEmpty(){
        return this.items.length === 1
    }
    clear(){
        this.items = []
    }
    size(){
        return this.items.length
    }
    min (){
        let last = this.arr[this.arr.length - 1]
        return this.items[last]
    }
}

下週預告

下週將練習隊列(Queue) 的題目,開始翻起算法書籍學習咯。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章