面試高頻算法詳解 | 棧與隊列篇

面試高頻算法詳解 | 棧與隊列篇

在互聯網招聘的面試環節中,手撕算法環節往往會與數據結構的考察相結合。各種經典的算法都離不開常用數據結構的支持。在之前的分享中,我們對鏈表結構進行了分析,由淺入深的掌握了鏈表的基本操作和變形算法。

今天,我們同樣將從最基礎的數據結構棧與隊列出發,挖掘常見線性結構的處理思想。

1 結構定義

棧也叫堆棧,是一種在操作系統以及CPU指令設計中經常使用的數據結構。由於棧對線性存儲元素的受限操作,使得其能夠在很多程序設計的場景中獲得特殊的效用。

棧的特點可以總結爲四個字“先進後出”,意味着最先存入棧中的元素最後才能被取出。棧的基本操作有入棧(PUSH)和出棧(POP),分別代表着存值與取值。同時,每個棧都有一個指針TOP,指向棧頂元素。棧規定只能在棧頂進行插入和刪除以及取棧頂元素。棧的插值與刪除示例可見下圖。
<>在這裏插入圖片描述

在C++ STL中爲我們方便的封裝好了模板<stack>,同時還有對棧的一系列常用操作:

stack<int> s; //棧的定義
s.empty(); //如果棧爲空返回true,否則返回false  
s.size(); //返回棧中元素的個數  
s.pop(); //刪除棧頂元素但不返回其值  
s.top(); //返回棧頂的元素,但不刪除該元素  
s.push(X); //在棧頂壓入新元素,參數X爲要壓入的元素

隊列

隊列是一種和棧十分類似的線性結構,根本區別在於插值與取值的位置不同。棧可以看作是半封閉式的結構,元素的進出只能在棧頂一個方向;而隊列可看作是中通的結構,如流水線式,一方進,一方出。因此隊列的特徵總結爲四個字即爲“先進先出”。

隊列爲表徵頭尾,有兩個指針隊頭(FRONT)與隊尾(REAR)分別指向隊列插入和刪除的位置。其插入和刪除的示例如下圖。
在這裏插入圖片描述
在C++ STL中同樣爲我們方便的封裝好了模板<queue>,同時還有對隊列的一系列常用操作:

queue<int> q; //隊列的定義
q.empty(); // 如果隊列爲空返回true,否則返回false  
q.size();  //返回隊列中元素的個數  
q.pop();  //刪除隊列首元素但不返回其值  
q.front(); // 返回隊首元素的值,但不刪除該元素  
q.push(X); //在隊尾壓入新元素,X爲要壓入的元素
q.back(); //返回隊列尾元素的值,但不刪除該元素

2 高頻算法

0x00 Leetcode 115 最小棧

設計一個支持 push,pop,top 操作,並能在常數時間內檢索到最小元素的棧。
push(x) – 將元素 x 推入棧中。
pop() – 刪除棧頂的元素。
top() – 獲取棧頂元素。
getMin() – 檢索棧中的最小元素。

解題思路

這是一道比較基礎的題,重點是掌握出入棧的順序。首先分析題目,需要在常數時間內檢索到最小元素,我們知道單個元素出棧本身就已經是常數時間。這意味着要在棧頂存放最小元素。

因此可以先設置一個存儲棧用於存放所有的元素;此外還需設置一個輔助棧來存放當前存儲棧中所有元素的最小值。如此一來,即可通過取出輔助棧棧頂的元素獲得存儲棧中的最小值。

代碼流程:(以插入序列4 3 2 5 6爲例)

PUSH操作
01 初始判斷。若存儲棧爲空,則說明第一個入棧的元素就是最小元素。此時存儲棧與輔助棧都需要插入新元素。插入4:
在這裏插入圖片描述

02 若存儲棧不爲空,則判斷新元素與輔助棧棧頂元素的大小。若新元素比輔助棧棧頂元素大,則只需在存儲棧中插入新元素;若新元素比輔助棧棧頂元素小或相等,說明新元素爲新的最小值,則需要在存儲棧和輔助棧中同時插入新元素。
在這裏插入圖片描述

03 重複執行02,可得:
在這裏插入圖片描述
POP操作
判斷存儲棧棧頂元素與輔助棧棧頂元素是否相等,如若相等,則兩個棧棧頂元素都需要刪去;否則,只需要刪去存儲棧中棧頂元素即可。

C++代碼

class MinStack {
public:
    /** initialize your data structure here. */
    stack<int> stack1;
    stack<int> stack2;

    void push(int x) {
        if(stack2.empty())
            stack2.push(x);
        else if(x <= stack2.top())
            stack2.push(x);
        stack1.push(x);
    }
    
    void pop() {
        if(stack2.top() == stack1.top())
            stack2.pop();
        stack1.pop();
    }
    
    int top() {
        return stack1.top();
    }
    
    int getMin() {
        return stack2.top();
    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(x);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->getMin();
 */

0x01 Leetcode 225 用隊列實現棧

使用隊列實現棧的下列操作:
push(x) – 元素x入棧
pop() – 移除棧頂元素
top() – 獲取棧頂元素
empty() – 返回棧是否爲空

解題思路
這題思路可參考上題,同樣也需要藉助另個輔助隊列用於臨時元素的存儲。實現過程可見動畫:
01 刪除棧頂元素
在這裏插入圖片描述
02 棧頂插入元素
在這裏插入圖片描述
C++代碼

class MyStack {
public:
    MyStack() {        
    }
    void push(int x) {
        std::queue<int> temp;
        temp.push(x);
        while(!data.empty()){
            temp.push(data.front());
            data.pop();
        }
        while(!temp.empty()){
            data.push(temp.front());
            temp.pop();
        }
    }
    int pop() {
        int x = data.front();
        data.pop();
        return x;
    }
    int top() {
        return data.front();
    }
    bool empty() {
        return data.empty();
    }
private:
    std::queue<int> data;
};

0x02 Leetcode 232 用棧實現隊列

使用棧實現的隊列下列操作:
push(x) – 將一個元素放入隊列的尾部
pop() – 從隊列首部移除元素
peek() – 返回隊列首部的元素
empty() – 返回隊列是否爲空

解題思路
思路與上題類似,同樣是兩個過程:
01 刪除隊列頭部元素;
在這裏插入圖片描述
02 隊列尾部插入元素;
在這裏插入圖片描述
C++代碼

class MyQueue {
public:
    MyQueue() {
    }
    void push(int x) {
        std::stack<int> temp_stack;
        while(!data.empty()){
            temp_stack.push(data.top());
            data.pop();
        }
        temp_stack.push(x);
        while(!temp_stack.empty()){
            data.push(temp_stack.top());
            temp_stack.pop();
        }
    }
    int pop() {
        int x = data.top();
        data.pop();
        return x;
    }
    int peek() {
        return data.top();
    }
    bool empty() {
        return data.empty();
    }
private:
    std::stack<int> data;
};

3 總結

棧和隊列是最基礎的數據結構之一,但是越基礎的東西有時候反而越能產生妙用。上面三個算法的難度雖然都不大,但是其中卻蘊含了對於線性表的充分理解。很多複雜的機制內部或許都是採用了比較簡潔的轉換,在不同場景下越基礎的結構或許越能夠帶來性能上的提升。

本來堆應該也放在這一篇中來講的,但是堆涉及到二叉樹的一些定義,所以打算放到下一篇跟二叉樹一起講。就醬,拜拜~

微信搜索業餘碼農:
回覆【秋招】可獲得計算機基礎知識總結資料;
回覆【算法】可獲得BAT高頻算法詳解;

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