leetcode經典題系列------棧和佇列
題目一:用佇列實現棧
描述:設計一個棧,支援如下操作,這些操作的演算法複雜度需要是常數級,O(1) , 棧的內部儲存資料的結構為佇列,佇列的方法只能包括push、peek(front)、pop、size、empty等標準的佇列方法
思路:利用兩個佇列實現棧
class MyStack{ public: MyStack(){} void push(int x){ std::queue<int>temp_queue; temp_queue.push(x); while (!_data.empty()) { temp_queue.push(_data.front()); _data.pop(); }while (!temp_queue.empty()) { _data.push(temp_queue.front()); temp_queue.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; };
題目二:使用棧實現佇列
描述:設計一個佇列,佇列支援如下操作,這些操作的演算法複雜度需要是常數級, O(1),佇列的內部儲存資料的結構為棧,棧的方法只能包括push、top、pop、size、 empty等標準的棧方法
思路:利用2個棧,在 佇列呼叫push的時候,利用臨時棧 調換元素次序
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; };
題目三:包含min函式的棧
描述:設計一個棧,支援如下操作,這些操作的算 法複雜度需要是常數級,O(1)
實現:
1.push(x) : 將元素x壓入棧中
2.pop() : 彈出(移除)棧頂元素
3.top() : 返回棧頂元素
4.getMin() : 返回棧內最小元素
思路:
1.1個變數MIN無法完成記錄棧中所有狀態下的最小值。
2.棧的每個狀態,都需要有一個變數記錄最小值。可以用一個最小值棧,記錄棧每個狀態下的最小值
class MinStack{ public: MinStack(){} void push(int x){ _data.push(x); if(_min.empty()){ _min.push(x); } else{ if(x>_min.top()){ x=_min.top(); } _min.push(x); } } void pop(){ _data.pop(); _min.pop(); } int top(){ return _data.top(); } int getMin(){ return _min.top(); } private: std::stack<int>_data; std::stack<int>_min; };
題目四:合法的出棧序列
描述:已知從1至n的數字序列,按順序入棧,每個數字入棧後即可出棧, 也可在棧中停留,等待後面的數字入棧出棧後,該數字再出棧,求該數字序列的出棧序列是否合法?
比如 3 2 5 4 1 合法 3 1 2 4 5不合法,因為1不可能在2之前
思路:
使用棧和佇列模擬入站出站操作
步驟:
1 出站結果儲存在佇列order中
2 按元素順序,將元素push進入棧
3 每push一次元素,即檢查是否和佇列首部元素相同,若相同則彈出佇列首元素,彈出戰頂元素,直到兩元素不同
4 若最終棧為空,說明序列合法,反之不合法
bool check_is_valid_order(std::queue<int>&order)//檢查序列儲存在佇列 { std::stack<int>S;//模擬戰 int n=(int)order.size();//n為序列長度,將1-n按順序入站 for (int i=1; i<=n; i++) { S.push(i); //將i入站 while (!S.empty()&&S.top()==order.front()) { S.pop(); order.pop();//只要S不空且佇列頭部與戰頂相同,即彈出元素 } } if(S.empty()!=false){//如果最終棧不空,則說明序列不合法 return false; } return true; }
題目五 簡單計算器
利用狀態機的概念,在操作符狀態或數字狀態下進行相應的處理
測試:
std::string s = "1+(2-3)";
Solution solve;
printf("%d\n", solve.calculate(s));
//字串轉數字 void changeStrToNum(std::string s){ int number=0; for(int i=0;i<s.length();i++){ if (s[i] >= '0' && s[i] <= '9'){ number = number * 10 + s[i] - '0'; } } printf("%d\n",number); }
class Solution { public: int calculate(std::string s) { //三個狀態 static const int STATE_BEGIN=0; static const int NUMBER_STATE=1; static const int OPERATION_STATE=2; std::stack<int>number_stack; std::stack<char>operation_stack; int number=0;//當前的數字 int STATE=STATE_BEGIN;//當前狀態 int compute_flag=0;//1 為準備計算 0 為 先不能計算 for(int i=0;i<s.length();i++){//遍歷字串 if(s[i]==' ') continue; switch (STATE) { case STATE_BEGIN: if(s[i]>='0'&&s[i]<='9'){//是數字 STATE=NUMBER_STATE;//當前應該轉換為數字狀態 } else{ STATE=OPERATION_STATE; } i--;//這次只是判斷,所以應該退格,因為for迴圈中i會遞增 break; case NUMBER_STATE: if(s[i]>='0'&&s[i]<='9'){//是數字 number=number*10+s[i]-'0';//字元轉數字 } else{//不是數字,說明到操作符了 number_stack.push(number); if(compute_flag==1){ //可以計算了 compute(number_stack, operation_stack); } number=0;//本次數字儲存完畢,清零 i--;//只是判斷下下次應該的狀態,所以i退格 STATE=OPERATION_STATE; } break; case OPERATION_STATE: if(s[i]=='+'||s[i]=='-'){ operation_stack.push(s[i]); compute_flag=1;//先標記為可以計算,如果遇到(,在標示為不可計算 } else if(s[i]=='('){//左括號,將要為數字模式,並且先不能計算 STATE=NUMBER_STATE; compute_flag=0; } else if(s[i]>='0'&&s[i]<='9'){ STATE=NUMBER_STATE; i--;//下次為數字模式,退格 } else if(s[i]==')'){//右括號,開始計算 compute(number_stack, operation_stack); } break; } } if(number!=0){ number_stack.push(number); compute(number_stack, operation_stack); } if(number==0&&number_stack.empty()){ int ii=0; return 0; } return number_stack.top(); } private: void compute(std::stack<int> &number_stack, std::stack<char> &operation_stack){ if (number_stack.size() < 2){ return; } int num2 = number_stack.top(); number_stack.pop(); int num1 = number_stack.top(); number_stack.pop(); if (operation_stack.top() == '+'){ number_stack.push(num1 + num2); } else if(operation_stack.top() == '-'){ number_stack.push(num1 - num2); } operation_stack.pop(); } };
題目六 :求陣列中第K大的數
描述:
已知一個未排序的陣列,求這個陣列中第K大的數字。 如,array = [3,2,1,5,6,4] , k = 2,return 5
思路:常規我們可以對陣列進行排序,然後找出第K大的數,時間複雜度為O(nlogn),有沒有更優化的方法,有的,利用二叉堆,時間負責度位nlogk
維護一個K大小的最小堆,堆中元素個數小於K時,新元素直接進入堆;否則,當 堆頂小於新元素時,彈出堆頂,將新元素加入堆。
解釋: 由於堆是最小堆,堆頂是堆中最小元素,新元素都會保證比堆頂小(否則新元素
替換堆頂),故堆中K個元素是已掃描的元素裡最大的K個;堆頂即為第K大的數。
int findKthLargest(std::vector<int>&nums,int k) { //構建最小堆 std::priority_queue<int, std::vector<int>, std::greater<int> > Q; for (int i=0; i<nums.size(); i++) { if(Q.size()<k){ Q.push(nums[i]); } else if(nums[i]>Q.top()){ Q.pop(); Q.push(nums[i]); } } return Q.top(); }
題目七 尋找中位數
描述:
設計一個數據結構,該資料結構動態維護一組資料,且支援如下操作: 1.新增元素: void addNum(int num),將整型num新增至資料結構中。 2.返回資料的中位數: double findMedian(),返回其維護的資料的中位數。
中位數定義:
1.若資料個數為奇數,中位數是該組數排序後中間的數。[1,2,3] -> 2 2.若資料個數為偶數,中位數是該組數排序後中間的兩個數字的平均值。[1,2,3,4] -> 2.5
分析:
最直觀的方法:
儲存結構使用陣列,每次新增元素或查詢中位數時對陣列排序,
再計算結果。
時間複雜度:
1.比如 若新增元素時排序,addNum複雜度O(n)[插入排序,最好為O(n),最差為n的平方],findMedian複雜度O(1)
2.若查詢中位數時排序,addNum複雜度O(1),findMedian複雜度 O(nlogn)
若新增元素或查詢中位數是隨機的操作,共n次操作,按上述思想,整體復 雜度最佳為O(n^2)
所以應該尋找最優的方法:
思路:利用堆的性質
動態維護一個最大堆與一個最小堆,最大堆儲存一半資料,最小堆儲存 一般資料,維持最大堆的堆頂比最小堆的堆頂小,並且2個差一個元素
比如 如果最後 最大堆和最小堆個數一樣,那麼中位數為兩個堆頂的平方,如果其中一個比另一個多一個元素,那麼中位數就是多一個元素的堆的堆頂
新增元素的時候對堆進行調整有3種情況:
這裡 最大堆簡寫為 A,最小堆簡寫為 B
情況一:A和B一樣多
a 新元素大於最大堆,新元素插入B
b 新元素小於最大堆,新元素插入A
情況二:A比B多1
a 新元素大於A,新元素插入B
b 新元素小於A,A堆頂插入B,A堆頂移除,然後A插入新元素
情況三:A比B少1
a 新元素小於B,新元素插入A
b 新元素大於B,B堆頂插入A,B堆頂移除,然後B插入新元素
class MedianFinder{ public: MedianFinder(){} void addNum(int num){ if(big_queue.empty()){ big_queue.push(num); return; } if (big_queue.size() == small_queue.size()){ if (num < big_queue.top()){ big_queue.push(num); } else{ small_queue.push(num); } } else if(big_queue.size() > small_queue.size()){ if (num > big_queue.top()){ small_queue.push(num); } else{ small_queue.push(big_queue.top()); big_queue.pop(); big_queue.push(num); } } else if(big_queue.size() < small_queue.size()){ if (num < small_queue.top()){ big_queue.push(num); } else{ big_queue.push(small_queue.top()); small_queue.pop(); small_queue.push(num); } } } double findMedian(){ if (big_queue.size() == small_queue.size()){ return (big_queue.top() + small_queue.top()) / 2; } else if (big_queue.size() > small_queue.size()){ return big_queue.top(); } return small_queue.top(); } private: std::priority_queue<double> big_queue; std::priority_queue<double, std::vector<double>, std::greater<double> > small_queue; };