这是上节课模拟队列的代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int a[N];
int t, h;
void init(){
t = -1;
h = -1;
}
void push(int x){
t ++;
a[t] = x;
}
void pop(){
h ++;
}
bool empty(){
if(t == h) return true;
else return false;
}
int query(){
return a[h + 1];
}
int main(){
int n;
cin >> n;
string s;
int x;
init();
for(int i = 0; i < n; i ++ ){
cin >> s;
if(s == "push"){
cin >> x;
push(x);
}
if(s == "pop"){
pop();
}
if(s == "empty"){
if(empty()){
cout << "YES" << endl;
}
else{
cout << "NO" << endl;
}
}
if(s == "query"){
int ans;
ans = query();
cout << ans << endl;
}
}
return 0;
}
大家自行参考,学习。
终终终于把最基础的链表,栈,队列给学习完了,现在我们看一些具体的应用实践。今天我们讲单调栈。
首先,我们大家要理解一下,上面说的数据结构不是固定的,数据结构的定义特点都是相同的,但是具体的实现方式却不尽相同。换句话说,只要是能实现并满足数据结构的定义特点的方式,都可以代表当前的数据结构,黑猫白猫,抓到耗子就是好猫。所以栈,队列并不是之前讲到的那种固定的格式。
那好,我们开始讲单调栈:
单调栈的应用场景通常有两种:1. 找出每个数前面第一个比它小的数 2. 找出每个数前面第一个比它小的数的下标。其他的都大同小异了。
我们来看一下这道题 单调栈
这道题就是经典的第一种类型的题目。
老规矩,第一步,我们理解一下题目,嗯,很简单,找出每个数前面第一个比它小的数,如果有,就输出,如果没有,就输出-1,那怎么实现的,朴素的做法肯定是将数一个个存入数组中,然后以这个数为起点,从前往后找。
假设存入3,4,2,7,5这五个数字,现在存到“4”这个数组,我们需要向前遍历数字,找到第一个比7小的数,也就是3,输出3,之后再存入2,找到第一个比2小的数,没有,那就输出-1,之后以此类推,我们看一下时间复杂度(简单来看,其实就是遍历两遍,就是n的平方)
第二步,用编程语言表示出来,首先我们想到的肯定是数组,用数组存入,设置指针从前往后存入,然后从当前指针的位置向前遍历。代码如下:
#include <iostream>
using namespace std;
const int N = 100010;
int stk[N], tt;
int main()
{
int n;
cin >> n;
for(int i = 1; i <= n; i ++ )
{
cin >> stk[i];
int j = i - 1;
while(stk[j] >= stk[i] && j) j --;
if(j == 0)printf("-1 ");
else printf("%d ",stk[j]);
}
return 0;
}
但是这样的时间复杂度是O(n方)比较大,为了降低时间复杂度,我们需要做第三步优化
第三步,由于我们对于这些数的操作中只关心第一个比当前插入的数小的数,再往之前的数就不会被考虑到了,比如3,4,2这三个数中,如果之后还要继续插入并查询数字,那么只会查询到2,或者返回-1,而不会出现3,4,所以我们操作的时候就可以将位置靠前(假定下标小的位置是靠前)并且后面还有较它小的数覆盖掉。实现的代码如下:
#include <iostream>
using namespace std;
const int N = 100010;
int stk[N], tt;
int main()
{
int n;
cin >> n;
while (n -- )
{
int x;
scanf("%d", &x);
while (tt && stk[tt] >= x) tt -- ;
if (!tt) printf("-1 ");
else printf("%d ", stk[tt]);
stk[ ++ tt] = x;
}
return 0;
}
代码中有两点要充分理解,第一,在这个代码与之前的不同就是将指针 j 变成了指针 tt ,从普通数组变成了一个栈(这点其实不太重要,因为栈本质上都是用数组来实现的)这个代码中最重要的一点还是这个栈的数的范围,是从下标为1的点到下标为 tt 的点,tt 本质上就是一个栈顶指针,它可以将不需要的数弹出这个栈,这个是理解这个单调栈最核心的一步。第二,在while (tt && stk[tt] >= x) tt -- ;这行代码中,前面的 tt 是来判断栈不为空的,这个和我们之前的栈的判空不太相同,这个也是另一种比较主流的判空,由于 tt 代表的是栈顶指针,在这个栈中栈底不会变化,所以当栈顶指针 tt 指向 0 时表示栈为空。
那么,tt 栈顶指针是如何将将位置靠前并且后面还有较它小的数弹出栈的呢?还是拿 3 4 2 5 7 这五个数字举例,代码先将 3 输入x内,之后查找,由于 tt = 0,发现栈为空,那么就输出 -1 ,之后使 tt 加一,并把 3 的值赋给下标为 1 的位置;下一个, 4 输入x内,这时 tt 在 3 的位置,3 小于 4,于是把 3 输出;下一个, 2 输入x内,这时 tt 在 4 的位置,不满足条件,于是 tt - 1 ,向前查找,注意这时栈顶指针的位置已经改变,这意味着 4 已经被弹出栈,以此类推,3 也被弹出栈,此时 tt = 0,栈空,输出 -1。之后的数字也就大同小异。
到这里,相信大家基本就将单调栈理解了
第四步,debug,跳过。
留一下课后练习吧,写出来 找出每个数前面第一个比它小的数的下标 这种类型的单调栈,我们刚刚讲的是 找出每个数前面第一个比它小的数 。本质上其实还是一样的方法,一样的理论。
下课!!!