1單調棧
1.1 單調棧的定義
什麼是單調棧,顧名思義,入棧時遵循單調原則。一般來說,可以求出一個元素向右或向左能擴展的最大長度,保證該區間內該元素最大或最小。
1.2 舉個栗子
假設我們有一個整數數列a,元素分別爲20, 10, 30,40。
對於第i個元素,我們要找到大於a[i]的第一個元素的位置。
這就適合用單調棧的性質來求解,我們可以認爲a[i+k]小於a[i]那麼就能繼續向右擴展,元素入棧,否則不能擴展,棧內元素出棧。
下面來模擬一下單調棧(單調遞減)的求解過程,我們要一直保持棧的單調性。
1:棧爲空,20入棧
2:棧頂元素爲20,10比20要小,10入棧
3:棧頂元素爲10,30比10要大,如果加入30,那麼單調性會被破壞,10出棧。此時棧頂元素爲20,如果加入30,還是會破壞單調性,故20出棧,這時棧爲空,30入棧。
4:棧頂元素爲30,40比30要大,如果加入40,破壞單調性,30出棧,40入棧。
那麼爲什麼一直維持棧內元素的單調性就一定是對的呢,我們先假設棧中含有元素,並且一直保持單調遞減的。如果元素還沒有被彈出,那麼肯定是還沒有遇到比他大的元素。如果元素被彈出了,那麼一定是第一次遇到比他大的數了,不然元素早就被彈出了。
實現時,棧內實際上是保存的元素的索引。這樣就能很快的定位。
1.3 僞代碼
for(遍歷數組) {
if(棧爲空 || 當前元素 <= 棧頂元素) 當前元素入棧
else {
while(棧非空 && 當前元素大於 > 棧頂元素) 棧頂元素出棧、執行操作
當前元素入棧
}
}
1.4 單調棧入門例題推薦
https://www.luogu.com.cn/problem/P5788
2 校招真題
2.1 題目描述
小Q在週末的時候和他的小夥伴來到大城市逛街,一條步行街上有很多高樓,共有n座高樓排成一行。
小Q從第一棟一直走到了最後一棟,小Q從來都沒有見到這麼多樓,所以他想知道他在每棟樓的位置處能看到多少棟樓呢?
當前面的樓的高度大於等於後面的樓時,後面的樓將被擋住。
2.2 分析
見代碼註釋
2.3 代碼實現
//
// Created by panda on 2020/6/19.
//
/*
* 6
* 5 3 8 3 2 5
* 3 3 5 4 4 4
* 從當前位置開始向左的一個單調遞增序列長度加上向右邊的一個單調遞增序列個數
* 向右看時:我們從最右邊開始掃描,然後維護一個單調遞減的棧,這樣棧中元素的個數就是我們能看到的樓房棟數
* 向左看時:我們從最左邊開始掃描,同樣維護一個單調遞減的棧,同樣棧中元素個個數就是我們能看到的樓房棟數
* 證明向右看時:如果要入棧的元素破壞了單調遞減的性質,那麼將要入棧的元素勢必要比棧頂元素要大,等價於高樓遮住了低樓
* 這樣從當前元素的左邊一個看時,是不會看見破環了單調遞減性質的棧頂元素,就行3 勢必會破壞 2(棧頂), 5單調遞減棧的性質,
* 那麼從3右邊的第一個8向右看時,絕對不會看到2,因爲3(棧頂),2,5不會滿足單調遞減。
* 既然3的左邊第一個都看不見,那麼3的左邊所有元素都會看不見
* 那麼2對於3左邊的元素來說就沒有貢獻了。
* 證明向右看時同理
*/
#include<cstdio>
using namespace std;
const int maxn = 100000 + 7;
int st[maxn]; //模擬棧,存放的時樓房的索引,這樣我們就能很快的知道是第幾棟樓
int left[maxn]; //left[i] 向左邊看時,能看到的樓房數
int right[maxn]; //right[i] 向右看時,能看到的樓房數
int building[maxn]; //building[i] 第i棟樓的高度
int top; //模擬棧頂指針
int n; //樓房總數
int main() {
while (scanf("%d", &n) == 1) {
for (int i = 1; i <= n; i++) scanf("%d", &building[i]);
//1, 向右看
top = 0;
for (int i = n; i >= 1; i--) {
if (top == 0 || building[i] < building[st[top]]) {
right[i] = top;
st[++top] = i;
} else {
right[i] = top;
while (top != 0 && building[i] >= building[st[top]]) --top;
st[++top] = i;
}
}
//2, 向左看
top = 0;
for (int i = 1; i <= n; i++) {
if (top == 0 || building[i] < building[st[top]]) {
left[i] = top;
st[++top] = i;
} else {
left[i] = top;
while (top != 0 && building[i] >= building[st[top]]) --top;
st[++top] = i;
}
}
for (int i = 1; i <= n; i++) {
printf("%d ", left[i] + right[i] + 1);
}
printf("\n");
}
return 0;
}