資料結構與演算法之單調棧
技術標籤:C++/C資料結構演算法演算法資料結構棧單調棧leetcode
資料結構與演算法之單調棧
原文來自個人部落格(求訪問/關注/收藏): https://bbing.com.cn/ CSDN個人部落格不定期轉載
單調棧
顧名思義, 單調棧就是其元素單調的棧, 滿足兩個特性:
- 是棧
- 棧元素單調遞減(<)或者單調遞增(>)
當然, 關於第二點也可以是單調不遞減(>=)或者單調不遞增(<=).
構造一個單調棧
從實踐出發, 看看怎麼構建一個單調棧;
比如有一個正整數列表: [2 1 3 4 7 5]
構建其中之一的單調不遞減棧:
- 列表是否空? 如果空則轉2, 否則轉3;
- 退出;
- 從列表中取出元素, 轉4;
- 棧是否空? 如果空則轉5, 否則轉6;
- 元素入棧, 轉1;
- 新元素與棧頂元素比較, 如果滿足大小關係, 入棧, 轉1; 否則, 出棧, 轉4;
按照上述演算法, 構建單調棧:
- 棧空, 則元素入棧(1->3->4->5)
2
- 新元素比棧頂元素小(1->3->4->6->4->5)
1
- 新元素比棧頂元素大, 入棧(1->3->4->6)
1 3
- 新元素比棧頂元素大, 入棧(1->3->4->6)
1 3 4
- 新元素比棧頂元素大, 入棧(1->3->4->6)
1 3 4 7
- 新元素比棧頂元素小, 則彈出棧頂元素, 直到新元素比棧頂元素小或者棧空(1->3->4->6->4->5->2)
1 3 4 5
以上, 已經有幾分像是一個狀態機, 入棧出棧都是狀態的變化.
上圖是將單調棧轉換成狀態和操作, 藍色字體表示狀態, 黑色字體表示操作, 實線表示狀態轉換, 虛線表示附加操作.
下面, 我們不妨嘗試使用狀態機實現從一個列表生成單調棧的函式.
單調棧的程式碼實現
首先, 我們定義幾個狀態, 根據上述的流程圖, 有以下幾個狀態:
開始/下一個/棧空/棧不空/滿足大小/不滿足大小/列表空/列表不空/退出
enum class STAT { START, NEXT, SEMPTY_T, SEMPTY_F, OP_T, OP_F, LEMPTY_T, LEMPTY_F, EXIT };
與狀態對應的, 還有一些操作:
判斷棧是否空/比較操作/判斷佇列是否空/從佇列中取下一個元素/入棧/出棧
auto sempty = [&]() -> bool {
return s.empty();
};
auto op = [&](const int &a) -> bool {
return fop(s.top(), a);
};
int lpos = 0;
auto lempty = [&]() -> bool {
return lpos >= list.size();
};
auto lnext = [&]() -> int {
return list[lpos++];
};
auto push = [&](const int &a) {
s.push(a);
};
auto pop = [&]() {
s.pop();
};
最後就是狀態之間的跳轉規則, 直接按照流程編寫跳轉規則就好
do
{
switch (stat)
{
case STAT::START:
stat = lempty() ? STAT::LEMPTY_T : STAT::LEMPTY_F;
break;
case STAT::NEXT:
a = lnext();
stat = sempty() ? STAT::SEMPTY_T : STAT::SEMPTY_F;
break;
case STAT::SEMPTY_T:
push(a);
stat = STAT::START;
break;
case STAT::SEMPTY_F:
stat = op(a) ? STAT::OP_T : STAT::OP_F;
break;
case STAT::OP_T:
push(a);
stat = STAT::START;
break;
case STAT::OP_F:
pop();
stat = sempty() ? STAT::SEMPTY_T : STAT::SEMPTY_F;
break;
case STAT::LEMPTY_T:
stat = STAT::EXIT;
break;
case STAT::LEMPTY_F:
stat = STAT::NEXT;
break;
}
} while (STAT::EXIT != stat);
下面是完整的程式碼:
#include <stack>
#include <vector>
#include <iostream>
#include <string>
using namespace std;
enum class STAT
{
START,
NEXT,
SEMPTY_T,
SEMPTY_F,
OP_T,
OP_F,
LEMPTY_T,
LEMPTY_F,
EXIT
};
template <typename FOP>
void mStack(stack<int> &s, const vector<int> &list, FOP &&fop)
{
auto sempty = [&]() -> bool {
return s.empty();
};
auto op = [&](const int &a) -> bool {
return fop(s.top(), a);
};
int lpos = 0;
auto lempty = [&]() -> bool {
return lpos >= list.size();
};
auto lnext = [&]() -> int {
return list[lpos++];
};
auto push = [&](const int &a) {
s.push(a);
};
auto pop = [&]() {
s.pop();
};
STAT stat = STAT::START;
int a = -1;
do
{
switch (stat)
{
case STAT::START:
stat = lempty() ? STAT::LEMPTY_T : STAT::LEMPTY_F;
break;
case STAT::NEXT:
a = lnext();
stat = sempty() ? STAT::SEMPTY_T : STAT::SEMPTY_F;
break;
case STAT::SEMPTY_T:
push(a);
stat = STAT::START;
break;
case STAT::SEMPTY_F:
stat = op(a) ? STAT::OP_T : STAT::OP_F;
break;
case STAT::OP_T:
push(a);
stat = STAT::START;
break;
case STAT::OP_F:
pop();
stat = sempty() ? STAT::SEMPTY_T : STAT::SEMPTY_F;
break;
case STAT::LEMPTY_T:
stat = STAT::EXIT;
break;
case STAT::LEMPTY_F:
stat = STAT::NEXT;
break;
}
} while (STAT::EXIT != stat);
}
int main()
{
vector<int> cs{2, 1, 3, 4, 7, 5};
stack<int> ss;
mStack(ss, cs, [](const int &a, const int &b) { return a < b; });
while (!ss.empty())
{
cout << ss.top() << " ";
ss.pop();
}
return 1;
}
聊一兩句狀態機
要寫狀態機, 需要明確有幾個狀態, 和狀態的對應操作, 也需要明確狀態間的跳轉規則;
個人認為, 狀態機的程式碼很容易維護, 只需要關注下一個狀態就行了, 需求變更的時候, 改起來非常的方便;
上面的狀態機肯定還有很多優化空間的, 但是目前還沒打算研究這個專題, 後期會專門系統地看看狀態機的編寫方法;
每日溫度
這是對應leetcode題739.
給一個每天的溫度列表, 返回一個列表, 表示至少多少天后的溫度比這一天高, 例如輸入溫度[73, 74, 75, 71, 69, 72, 76, 73], 返回[1, 1, 4, 2, 1, 1, 0, 0]。
這是比較經典的單調棧的例子, “第一個比當前大/小的元素”.
我們可以從後往前構造一個單調遞減棧, 棧中元素是溫度的下標, 用溫度大小做比較;
程式碼如下:
vector<int> dailyTemperatures(vector<int> &T)
{
stack<int> st;
int si = T.size();
vector<int> rd(si);
for (int i = si - 1; i >= 0; i--)
{
while (!st.empty() && T[i] >= T[st.top()])
{
st.pop();
}
if (!st.empty())
{
rd[i] = st.top() - i;
}
else
{
rd[i] = 0;
}
st.push(i);
}
return rd;
}