面試高頻算法詳解 | 棧與隊列篇
在互聯網招聘的面試環節中,手撕算法環節往往會與數據結構的考察相結合。各種經典的算法都離不開常用數據結構的支持。在之前的分享中,我們對鏈表結構進行了分析,由淺入深的掌握了鏈表的基本操作和變形算法。
今天,我們同樣將從最基礎的數據結構棧與隊列出發,挖掘常見線性結構的處理思想。
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高頻算法詳解;