c++算法学习笔记 (15) 单调栈与单调队列

核心思想是先暴力想一遍,然后找没有用到的元素并删掉看剩余元素有无单调性。若有,就用单调栈/单调队列/二分来优化

1.单调栈

给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1。

输入格式

第一行包含整数 N,表示数列长度。

第二行包含 N 个整数,表示整数数列。

输出格式

共一行,包含 N 个整数,其中第 i 个数表示第 i 个数的左边第一个比它小的数,如果不存在则输出 −1。

数据范围

1≤N≤10^5
1≤数列中元素≤10^9

输入样例:
5
3 4 2 7 5
输出样例:
-1 3 -1 2 2

代码:这里往右遍历,如果遇到一个更小的数,说明之前存的数全部没有用了,所以全部弹出栈。

#include <iostream>
using namespace std;
const int N = 100010;
int n;
int stk[N], tt; // i左边的数存入stack,且满足单增栈
int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        int x;
        cin >> x;
        while (tt && stk[tt] >= x) 
        // 栈非空&&栈顶元素>=x  栈内遇见一个>=x的数就弹出,保证插入x后仍为单增栈
            tt--;
        if (tt) // 非空
            cout << stk[tt] << " ";
        else // 空
            cout << -1 << " ";
        stk[++tt] = x;
    }

    return 0;
}

 2.单调队列

 滑动窗口

给定一个大小为 n≤10^6 的数组。

有一个大小为 k 的滑动窗口,它从数组的最左边移动到最右边。

你只能在窗口中看到 k 个数字。

每次滑动窗口向右移动一个位置。

以下是一个例子:

该数组为 [1 3 -1 -3 5 3 6 7],k 为 33。

窗口位置最小值最大值
[1 3 -1] -3 5 3 6 7-13
1 [3 -1 -3] 5 3 6 7-33
1 3 [-1 -3 5] 3 6 7-35
1 3 -1 [-3 5 3] 6 7-35
1 3 -1 -3 [5 3 6] 736
1 3 -1 -3 5 [3 6 7]37

你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。

输入格式

输入包含两行。

第一行包含两个整数 n 和 k,分别代表数组长度和滑动窗口的长度。

第二行有 n 个整数,代表数组的具体数值。

同行数据之间用空格隔开。

输出格式

输出包含两个。

第一行输出,从左至右,每个位置滑动窗口中的最小值。

第二行输出,从左至右,每个位置滑动窗口中的最大值。

输入样例:
8 3
1 3 -1 -3 5 3 6 7
输出样例:
-1 -3 -3 -3 3 3
3 3 5 5 6 7

代码:无用元素1:不在窗口的元素;无用元素2:在窗口中最小值>要进来的元素,之前的最小值删去,保留新的最小值。

#include <iostream>
using namespace std;
const int N = 1000010;
int n, k;
int a[N], q[N]; // q[]存元素下标
int main()
{
    scanf("%d %d", &n, &k);
    int hh = 0, tt = -1;
    for (int i = 0; i < n; i++)
    {
        scanf("%d", &a[i]);
    }
    for (int i = 0; i < n; i++) // 输出最小元素
    {
        // 判断队头是否已经溢出窗口
        if (hh <= tt && i - k + 1 > q[hh]) // 不用while因为每次窗口只滑动一格
        {
            hh++;
        }
        while (hh <= tt && a[q[tt]] > a[i])
        { // 如果要进来的元素<队尾元素
            tt--;
        }
        q[++tt] = i; // 这里的位置不能变
        if (i >= k - 1)
        {
            printf("%d ", a[q[hh]]);
        }
    }
    cout << endl;
    hh = 0, tt = -1;//记得要重置!!!!
    for (int i = 0; i < n; i++) // 输出最大元素
    {
        // 判断队头是否已经溢出窗口
        if (hh <= tt && i - k + 1 > q[hh]) // 不用while因为每次窗口只滑动一格
        {
            hh++;
        }
        while (hh <= tt && a[q[tt]] < a[i])
        { // 如果要进来的元素>队尾元素
            tt--;
        }
        q[++tt] = i; // 这里的位置不能变
        if (i >= k - 1)
        {
            printf("%d ", a[q[hh]]);
        }
    }
    return 0;
}

  • 12
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值