AcWing 830.单调栈

题目传送门

一、单调栈原理

单调栈里装的是什么?

动态维护一个栈,把后续问题的答案都维持在这个栈中,把肯定不再有用的数字从栈中干掉。

二、理解与感悟

1、 动态维护,随进随维护,不是预处理。

2、可以将(O(N^2))的时间复杂度降为(O(N))

3、此类“左侧(或右侧)最近”,比自己大的(或小的),用单调栈。

4、算法步骤:
(1)不管是向左看还是向右看,都先读到数组中。
(2)代码框架:

int n;
stack<int> stk; //装的是序号
const int N = 1010;
int a[N];   //a[stk.top()]是指栈顶元素的值
int res[N]; //装的是序号 a[res[i]]是真正的数值

for(int i=1;i<=n;i++){//从左到右
   while(!stk.empty() && a[stk.top()]>=a[i]) {//栈顶元素找到了右侧第一个比自己小的
      res[stk.top()]=i;
      stk.pop();
  }
  stk.push(i);
}

for(int i=1;i<=n;i++){
   if(res[i]) cout<<a[res[i]]<<" ";
   else cout<<"-1 ";
}

三、左边第一个比我小

#include <bits/stdc++.h>

using namespace std;
const int N = 100010;
int n;
int a[N];
int stk[N], tt;
int res[N];//记录“左边比我小的数字”编号,值的描述:a[res[i]]

/**
 测试用例:左边第一个比我小
 6
 6 10 3 7 4 12

 结果:
 -1 6 -1 3 3 4
 */
int main() {
    //读入到数组,安全又健康
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    //从右向左遍历每一个数字,不断向左侧进发
    // 注意这里的遍历方向发生了变化,因为我们是要找到左边比我小的元素的位置
    for (int i = n; i >= 1; i--) {
        //如果栈不空,并且栈顶元素大于遍历到的数字,那么这些数字就是已经找到了“左边比我小的数字”
        while (tt && a[stk[tt]] > a[i]) {
            res[stk[tt]] = i;//记录下谁是谁的“左边比我小的数字”,然后退群~
            tt--;
        }
        //自己还没有找到“左边比我小的数字”,我还需要继续努力~
        stk[++tt] = i;
    }
    //正序输出结果
    for (int i = 1; i <= n; i++) {
        //如果找到了它的“左边比我小的数字”,那么res[i]>0,否则就是没有找到
        if (res[i]) cout << a[res[i]] << ' ';
        else cout << "-1 ";
    }
    return 0;
}

四、左边第一个比我小(STL)

#include <bits/stdc++.h>

using namespace std;
const int N = 100010;
int n;
int a[N];
stack<int> stk;
int res[N];//记录“左边比我小的数字”编号,值的描述:a[res[i]]

/**
 测试用例:左边第一个比我小
 6
 6 10 3 7 4 12

 结果:
 -1 6 -1 3 3 4
 */
int main() {
    //读入到数组,安全又健康
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    //从右向左遍历每一个数字,不断向左侧进发
    // 注意这里的遍历方向发生了变化,因为我们是要找到左边比我小的元素的位置
    for (int i = n; i >= 1; i--) {
        //如果栈不空,并且栈顶元素大于遍历到的数字,那么这些数字就是已经找到了“左边比我小的数字”
        while (!stk.empty() && a[stk.top()] > a[i]) {
            res[stk.top()] = i;//记录下谁是谁的“左边比我小的数字”,然后退群~
            stk.pop();
        }
        //自己还没有找到“左边比我小的数字”,我还需要继续努力~
        stk.push(i);
    }
    //正序输出结果
    for (int i = 1; i <= n; i++) {
        //如果找到了它的“左边比我小的数字”,那么res[i]>0,否则就是没有找到
        if (res[i]) cout << a[res[i]] << ' ';
        else cout << "-1 ";
    }
    return 0;
}

五、左边第一个比我大

#include <bits/stdc++.h>

using namespace std;

const int N = 100010;
int n;
int a[N];
int stk[N], tt;
int res[N];//记录“左边比我大的数字”编号,值的描述:a[res[i]]

/**
 测试用例:左边第一个比我大
 6
 6 10 3 7 4 12

 结果:
 -1 -1 10 10 7 -1
 */
int main() {
    //读入到数组,安全又健康
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    //从右向左遍历每一个数字,不断向左侧进发
    // 注意这里的遍历方向发生了变化,因为我们是要找到左边比我小的元素的位置
    for (int i = n; i >= 1; i--) {
        //如果栈不空,并且栈顶元素大于遍历到的数字,那么这些数字就是已经找到了“左边比我大的数字”
        while (tt && a[stk[tt]] < a[i]) {
            res[stk[tt]] = i;//记录下谁是谁的“左边比我大的数字”,然后退群~
            tt--;
        }
        //自己还没有找到“左边比我大的数字”,我还需要继续努力~
        stk[++tt] = i;
    }
    //正序输出结果
    for (int i = 1; i <= n; i++) {
        //如果找到了它的“左边比我大的数字”,那么res[i]>0,否则就是没有找到
        if (res[i]) cout << a[res[i]] << ' ';
        else cout << "-1 ";
    }
    return 0;
}

六、左边第一个比我大(STL)

#include <bits/stdc++.h>

using namespace std;

const int N = 100010;
int n;
int a[N];
stack<int> stk;
int res[N];//记录“左边比我大的数字”编号,值的描述:a[res[i]]

/**
 测试用例:左边第一个比我大
 6
 6 10 3 7 4 12

 结果:
 -1 -1 10 10 7 -1
 */
int main() {
    //读入到数组,安全又健康
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    //从右向左遍历每一个数字,不断向左侧进发
    // 注意这里的遍历方向发生了变化,因为我们是要找到左边比我小的元素的位置
    for (int i = n; i >= 1; i--) {
        //如果栈不空,并且栈顶元素大于遍历到的数字,那么这些数字就是已经找到了“左边比我大的数字”
        while (!stk.empty() && a[stk.top()] < a[i]) {
            res[stk.top()] = i;//记录下谁是谁的“左边比我大的数字”,然后退群~
            stk.pop();
        }
        //自己还没有找到“左边比我大的数字”,我还需要继续努力~
        stk.push(i);
    }
    //正序输出结果
    for (int i = 1; i <= n; i++) {
        //如果找到了它的“左边比我大的数字”,那么res[i]>0,否则就是没有找到
        if (res[i]) cout << a[res[i]] << ' ';
        else cout << "-1 ";
    }
    return 0;
}

七、右边第一个比我小

#include <bits/stdc++.h>

using namespace std;

const int N = 100010;
int n;
int a[N];
int stk[N], tt;
int res[N];//记录“右边比我小的数字”编号,值的描述:a[res[i]]

/**
 测试用例:右边第一个比我小
 6
 6 10 3 7 4 12

 结果:
 3 3 -1 4 -1 -1
 */
int main() {
    //读入到数组,安全又健康
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    //从左向右遍历每一个数字
    for (int i = 1; i <= n; i++) {
        //如果栈不空,并且栈顶元素大于遍历到的数字,那么这些数字就是已经找到了“右边比我小的数字”
        while (tt && a[stk[tt]] > a[i]) {
            res[stk[tt]] = i;//记录下谁是谁的“右边比我小的数字”,然后退群~
            tt--;
        }
        //自己还没有找到“右边比我小的数字”,我还需要继续努力~
        stk[++tt] = i;
    }
    //正序输出结果
    for (int i = 1; i <= n; i++) {
        //如果找到了它的“右边比我小的数字”,那么res[i]>0,否则就是没有找到
        if (res[i]) cout << a[res[i]] << ' ';
        else cout << "-1 ";
    }
    return 0;
}

八、右边第一个比我小(STL)

#include <bits/stdc++.h>

using namespace std;
const int N = 100010;
int n;
int a[N];
stack<int> stk;
int res[N];//记录“右边比我小的数字”编号,值的描述:a[res[i]]

/**
 右边第一个比我小
 测试用例:
 6
 6 10 3 7 4 12

 结果:
 3 3 -1 4 -1 -1
 */
int main() {
    //读入到数组,安全又健康
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    //从左向右遍历每一个数字
    for (int i = 1; i <= n; i++) {
        //如果栈不空,并且栈顶元素大于遍历到的数字,那么这些数字就是已经找到了“右边比我小的数字”
        while (!stk.empty() && a[stk.top()] > a[i]) {
            res[stk.top()] = i;//记录下谁是谁的“右边比我小的数字”,然后退群~
            stk.pop();
        }
        //自己还没有找到“右边比我小的数字”,我还需要继续努力~
        stk.push(i);
    }
    //正序输出结果
    for (int i = 1; i <= n; i++) {
        //如果找到了它的“右边比我小的数字”,那么res[i]>0,否则就是没有找到
        if (res[i]) cout << a[res[i]] << ' ';
        else cout << "-1 ";
    }
    return 0;
}

九、右边第一个比我大

#include <bits/stdc++.h>

using namespace std;
const int N = 100010;
int n;
int a[N];
int stk[N], tt;
int res[N];//记录“右边比我小的数字”编号,值的描述:a[res[i]]

/**
 测试用例:右边第一个比我大
 6
 6 10 3 7 4 12

 结果:
 10 12 7 12 12 -1
 */
int main() {
    //读入到数组,安全又健康
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    //从左向右遍历每一个数字
    for (int i = 1; i <= n; i++) {
        //如果栈不空,并且栈顶元素小于遍历到的数字,那么这些数字就是已经找到了“右边比我大的数字”
        while (tt && a[stk[tt]] < a[i]) {
            res[stk[tt]] = i;//记录下谁是谁的“右边比我大的数字”,然后退群~
            tt--;
        }
        //自己还没有找到“右边比我大的数字”,我还需要继续努力~
        stk[++tt] = i;
    }
    //正序输出结果
    for (int i = 1; i <= n; i++) {
        //如果找到了它的“右边比我大的数字”,那么res[i]>0,否则就是没有找到
        if (res[i]) cout << a[res[i]] << ' ';
        else cout << "-1 ";
    }
    return 0;
}

十、右边第一个比我大(STL)

#include <bits/stdc++.h>

using namespace std;
const int N = 100010;
int n;
int a[N];
stack<int> stk;
int res[N];//记录“右边比我小的数字”编号,值的描述:a[res[i]]

/**
 测试用例:右边第一个比我大
 6
 6 10 3 7 4 12

 结果:
 10 12 7 12 12 -1
 */
int main() {
    //读入到数组,安全又健康
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    //从左向右遍历每一个数字
    for (int i = 1; i <= n; i++) {
        //如果栈不空,并且栈顶元素小于遍历到的数字,那么这些数字就是已经找到了“右边比我大的数字”
        while (!stk.empty() && a[stk.top()] < a[i]) {
            res[stk.top()] = i;//记录下谁是谁的“右边比我大的数字”,然后退群~
            stk.pop();
        }
        //自己还没有找到“右边比我大的数字”,我还需要继续努力~
        stk.push(i);
    }
    //正序输出结果
    for (int i = 1; i <= n; i++) {
        //如果找到了它的“右边比我大的数字”,那么res[i]>0,否则就是没有找到
        if (res[i]) cout << a[res[i]] << ' ';
        else cout << "-1 ";
    }
    return 0;
}

十一、视频讲解

https://www.ixigua.com/6925435220292436494

原文地址:https://www.cnblogs.com/littlehb/p/15247256.html