LeetCode-棧-隊列-堆
文章目錄
- LeetCode 225 - Implement Stack using Queues - 用隊列實現棧 - easy
- LeetCode 232 - Implement Queue using Stack- 用棧實現隊列 - easy
- LeetCode 155 - Min Stack - 最小棧 - easy
- Leetcode 946 - Validate Stack Sequences - 驗證棧序列 - Medium
- LeetCode 224 - Basic Calculator - Hard
- LeetCode 215 - Kth Largest Element in an Array - 數組中第k大的數 - easy
- LeetCode 295 - Find Median from Data Stream - 尋找中位數 - Hard
LeetCode 225 - Implement Stack using Queues - 用隊列實現棧 - easy
使用隊列實現棧的下列操作:
push(x) -- 元素 x 入棧
pop() -- 移除棧頂元素
top() -- 獲取棧頂元素
empty() -- 返回棧是否爲空
注意:
你只能使用隊列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 這些操作是合法的
你所使用的語言也許不支持隊列。 你可以使用 list 或者 deque(雙端隊列)來模擬一個隊列 , 只要是標準的隊列操作即可。
你可以假設所有操作都是有效的(例如, 對一個空的棧不會調用 pop 或者 top 操作)。
題幹要求,不能使用隊列的back函數,也就是純單向隊列。
那麼就需要藉助臨時隊列。
class MyStack {
public:
/** Initialize your data structure here. */
MyStack() {
}
/** Push element x onto stack. */
void push(int x) {
// 構造臨時隊列
std::queue<int> queTmp;
queTmp.push(x);
int nTmp = 0;
while(!queData.empty())
{
nTmp = queData.front();
queTmp.push(nTmp);
queData.pop();
}
while(!queTmp.empty())
{
nTmp = queTmp.front();
queData.push(nTmp);
queTmp.pop();
}
//queData.swap(queTmp);
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
int nTmp = queData.front();
queData.pop();
return nTmp;
}
/** Get the top element. */
int top() {
return queData.front();
}
/** Returns whether the stack is empty. */
bool empty() {
return queData.empty();
}
private:
std::queue<int> queData;
};
不知道爲啥,不用nTmp,直接push(front())
的話內存消耗要更大。
LeetCode 232 - Implement Queue using Stack- 用棧實現隊列 - easy
使用棧實現隊列的下列操作:
push(x) -- 將一個元素放入隊列的尾部。
pop() -- 從隊列首部移除元素。
peek() -- 返回隊列首部的元素。
empty() -- 返回隊列是否爲空。
class MyQueue {
public:
/** Initialize your data structure here. */
MyQueue() {
}
/** Push element x to the back of queue. */
void push(int x) {
std::stack<int> sTmp;
while(!sData.empty())
{
sTmp.push(sData.top());
sData.pop();
}
sData.push(x);
while(!sTmp.empty())
{
sData.push(sTmp.top());
sTmp.pop();
}
// 這裏就不能用swap了
//sTmp.swap(sData);
}
/** Removes the element from in front of queue and returns that element. */
int pop() {
int n = sData.top();
sData.pop();
return n;
}
/** Get the front element. */
int peek() {
return sData.top();
}
/** Returns whether the queue is empty. */
bool empty() {
return sData.empty();
}
private:
std::stack<int> sData;
};
LeetCode 155 - Min Stack - 最小棧 - easy
設計一個支持 push ,pop ,top 操作,並能在常數時間(時間複雜度O(1))內檢索到最小元素的棧。
push(x) —— 將元素 x 推入棧中。
pop() —— 刪除棧頂的元素。
top() —— 獲取棧頂元素。
getMin() —— 檢索棧中的最小元素。
示例:
輸入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
輸出:
[null,null,null,null,-3,null,0,-2]
解釋:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
前兩道題時間複雜度都是O(n)。 思路是用空間換時間,但是隻4增加一個私有變量nMin是無法記錄所有狀態的,比如pop之後,,,
一個變量不行,就增加多個變量:最小值棧,top記錄當前狀態的最小值。
需要注意,訪問容器一定要判斷是否爲空。
class MinStack {
public:
/** initialize your data structure here. */
MinStack() {
}
void push(int x) {
sData.push(x);
if(sMin.empty())
{
sMin.push(x);
}
else
{
sMin.push( x < sMin.top() ? x : sMin.top());
}
}
void pop() {
sData.pop();
sMin.pop();
}
int top() {
return sData.top();
}
int getMin() {
return sMin.top();
}
private:
std::stack<int> sData;
// 最小值棧,top記錄當前狀態的最小值。
std::stack<int> sMin;
};
Leetcode 946 - Validate Stack Sequences - 驗證棧序列 - Medium
類似的還有poj平臺1363 Rails。
給定 pushed 和 popped 兩個序列,每個序列中的 值都不重複,只有當它們可能是在最初空棧上進行的推入 push 和彈出 pop 操作序列的結果時,返回 true;否則,返回 false 。
示例 1:
輸入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
輸出:true
解釋:我們可以按以下順序執行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:
輸入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
輸出:false
解釋:1 不能在 2 之前彈出。
思路是,藉助棧存儲pushed序列,一段判斷pushed.top() == poped.front()
,匹配則均pop。如果最後棧空了,則返回true。
這個複雜度爲O(n),不是n方。
class Solution {
public:
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
stack<int> s;
int nSize = pushed.size();
int j = 0;
for (int i = 0; i < nSize; ++i)
{
s.push(pushed[i]);
while(!s.empty() && s.top() == popped[j])
{
s.pop();
++j;
}
}
return s.empty() ? true : false;
}
};
LeetCode 224 - Basic Calculator - Hard
實現一個基本的計算器來計算一個簡單的字符串表達式的值。
字符串表達式可以包含左括號 ( ,右括號 ),加號 + ,減號 -,非負整數和空格 。
示例 :
輸入: "(1+(4+5+2)-3)+(6+8)"
輸出: 23
說明:
你可以假設所給定的表達式都是有效的。
請不要使用內置的庫函數 eval。
需要基於數字棧和符號棧,實現逆波蘭式,也叫後綴表達式。
設計狀態機:
框架:
for (int i = 0; i < nLen; ++i)
{
if ( s[i] == ' ' ) continue;
switch(state)
{
case STATE_BEGIN:
// judge STATE
--i
break;
case STATE_NUM:
// if num, number = ...
// else, compute, then STATE_OP
break;
case STATE_OP:
// if +-, push, then set bCalc
// if (, STATE_NUM, unset bCalc
// if ), compute
// if num, STATE_NUM
break;
}
}
if (nRes != 0)
{
stackNum.push(nRes);
compute(stackNum, stackOp);
}
else if( nRes == 0 && stackNum.empty())
{
return 0;
}
完整版:
class Solution {
public:
int calculate(string s) {
// 測試中有大於(2**31-1的輸入,所以用long
std::stack<long> stackNum;
std::stack<char> stackOp;
long nNum = 0;
enum State state = STATE_BEGIN;
bool bCalc = false;
int nLen = s.length();
for (int i = 0; i < nLen; ++i)
{
if (s[i] == ' ') continue;
switch (state)
{
case STATE_BEGIN:
// judge STATE
state = (s[i] >= '0' && s[i] <= '9')
? STATE_NUM
: STATE_OP;
--i;
break;
case STATE_NUM:
// if num, number = ...
// else, compute, then STATE_OP
if (s[i] >= '0' && s[i] <= '9')
{
nNum = nNum * 10 + s[i] - '0';
}
else
{
stackNum.push(nNum);
if (bCalc)
{
compute(stackNum, stackOp);
}
nNum = 0;
--i;
state = STATE_OP;
}
break;
case STATE_OP:
// if +-, push, then set bCalc
// if (, STATE_NUM, unset bCalc
// if ), compute
// if num, STATE_NUM
if (s[i] == '+' || s[i] == '-')
{
stackOp.push(s[i]);
bCalc = true;
}
else if (s[i] == '(')
{
state = STATE_NUM;
bCalc = false;
}
else if (s[i] == ')')
{
compute(stackNum, stackOp);
}
else if (s[i] >= '0' && s[i] <= '9')
{
state = STATE_NUM;
--i;
}
break;
}
}
if (nNum != 0)
{
stackNum.push(nNum);
compute(stackNum, stackOp);
}
else if (nNum == 0 && stackNum.empty())
{
return 0;
}
return stackNum.top();
}
private:
void compute(std::stack<long> &stackNum, std::stack<char> &stackOp)
{
if (stackNum.size() < 2) return;
long nNum1 = stackNum.top();
stackNum.pop();
long nNum2 = stackNum.top();
stackNum.pop();
char cOp = stackOp.top();
if (cOp == '+')
{
stackNum.push(nNum2 + nNum1);
}
else if (cOp == '-')
{
stackNum.push(nNum2 - nNum1);
}
stackOp.pop();
}
private:
enum State {
STATE_BEGIN = 0,
STATE_NUM = 1,
STATE_OP = 2
};
};
LeetCode 215 - Kth Largest Element in an Array - 數組中第k大的數 - easy
在未排序的數組中找到第 k 個最大的元素。請注意,你需要找的是數組排序後的第 k 個最大的元素,而不是第 k 個不同的元素。
示例 1:
輸入: [3,2,1,5,6,4] 和 k = 2
輸出: 5
示例 2:
輸入: [3,2,3,1,2,4,5,5,6] 和 k = 4
輸出: 4
說明:
你可以假設 k 總是有效的,且 1 ≤ k ≤ 數組的長度。
二叉堆
最大/小二叉堆:最大/小值先出的完全二叉樹。
最大堆:
最小堆:
堆又叫優先級隊列,對應std::priority_queue
容器。
int main()
{
std::vector<int> v({ 6, 10, 1, 7, 99, 4, 33 });
// 默認最大堆
std::priority_queue<int> big_heap(v.begin(), v.end());
// 最小堆
std::priority_queue<int, std::vector<int>, std::greater<int>> small_heap(v.begin(), v.end());
// 最大堆
std::priority_queue<int, std::vector<int>, std::greater<int>> big_heap_1;
// 99
std::cout << big_heap.top() << std::endl;
// 1
std::cout << small_heap.top() << std::endl;
big_heap.push(1000);
// 1000
std::cout << big_heap.top() << std::endl;
big_heap.pop();
big_heap.pop();
big_heap.pop();
// 10
std::cout << big_heap.top() << std::endl;
return 0;
}
解
要想讓top爲第k大的數,那麼二叉樹其餘元素爲大於堆頂的k-1個數,也就是需要藉助一個元素個數爲k的最小堆。
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
std::priority_queue<int, std::vector<int>, std::greater<int>>
small_heap;
int nSize = nums.size();
for(int i = 0; i < nSize; ++i)
{
if (small_heap.size() < k)
{
small_heap.push(nums[i]);
}
else if ( nums[i] > small_heap.top())
{
small_heap.pop();
small_heap.push(nums[i]);
}
}
return small_heap.top();
}
};
LeetCode 295 - Find Median from Data Stream - 尋找中位數 - Hard
中位數是有序列表中間的數。如果列表長度是偶數,中位數則是中間兩個數的平均值。
例如,
[2,3,4] 的中位數是 3
[2,3] 的中位數是 (2 + 3) / 2 = 2.5
設計一個支持以下兩種操作的數據結構:
void addNum(int num) - 從數據流中添加一個整數到數據結構中。
double findMedian() - 返回目前所有元素的中位數。
示例:
addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3)
findMedian() -> 2
進階:
- 如果數據流中所有整數都在 0 到 100 範圍內,你將如何優化你的算法?
- 如果數據流中 99% 的整數都在 0 到 100 範圍內,你將如何優化你的算法?
思路
最笨的方法,每次添加後都對數組排序,這樣addNum爲O(n),findMedian爲O(1);若查找中位數時排序,則addNum爲O(1),findMedian爲O(nlogn)。
因爲兩種操作都是隨機的,則n次操作最理想的複雜度爲n方。
思路是,用最大堆和最小堆各存一半數據,保持最大堆top比最小堆top小。
搞清楚,最小堆存儲大的那一半數據。
添加元素3種情況。
當兩個堆size相同時:
- 新元素小於兩個top,則添加到小的那半數據裏(最大堆);
- 新元素大於兩個top,則添加到大的那半數據裏(最小堆)。
最大堆比最小堆多一個元素:
- 新元素大於最大堆top,直接push進較少的最小堆裏;
- 新元素小於最大堆top,則肯定要push進最大堆,但首先要把top加進最小堆。
最大堆比最小堆少一個元素,類比上一種情況即可。
代碼
class MedianFinder {
public:
/** initialize your data structure here. */
MedianFinder() {
}
void addNum(int num) {
if (big_heap.empty())
{
big_heap.push(num);
return;
}
int nBigHeap = big_heap.size();
int nSmallHeap = small_heap.size();
if (nBigHeap == nSmallHeap)
{
if (num < big_heap.top())
{
big_heap.push(num);
}
else
{
small_heap.push(num);
}
}
else if (nBigHeap > nSmallHeap)
{
if (num > big_heap.top())
{
small_heap.push(num);
}
else
{
small_heap.push(big_heap.top());
big_heap.pop();
big_heap.push(num);
}
}
else if (nBigHeap < nSmallHeap)
{
if (num < small_heap.top())
{
big_heap.push(num);
}
else
{
big_heap.push(small_heap.top());
small_heap.pop();
small_heap.push(num);
}
}
}
double findMedian() {
if (big_heap.size() == small_heap.size())
{
return (big_heap.top() + small_heap.top()) / 2.0;
}
return (big_heap.size() > small_heap.size())
? big_heap.top()
: small_heap.top();
}
private:
// 用最大堆和最小堆各存一半數據
// 保持最大堆top比最小堆top小
std::priority_queue<int> big_heap;
std::priority_queue<int, std::vector<int>, std::greater<int>> small_heap;
};