模拟栈
实现一个栈,栈初始为空,支持四种操作:
push x
– 向栈顶插入一个数 x𝑥;pop
– 从栈顶弹出一个数;empty
– 判断栈是否为空;query
– 查询栈顶元素。
现在要对栈进行 𝑀 个操作,其中的每个操作 3 和操作 4 都要输出相应的结果。
int e[N];//用来存储栈里面的元素
int tt;//用来表示栈顶
1.Push函数(插入元素功能)
void Push(int x){
e[++tt] = x;
}
就是在e[]中插入一个新的元素。
2.Pop函数(从栈顶弹出一个数)
void Pop(){
tt--;
}
就是相当于栈顶减一。
3.Empty函数(判断栈里面是否为空)
bool Empty(){
if (tt > 0)
return false;
else
return true;
}
如果此时栈顶不为0,说明栈里面有元素。
4.Query函数(返回栈顶元素)
int Query(){
return e[tt];
}
返回栈顶元素。
单调队列
对于队列,最经典的题目就是滑动窗口。
给定一个大小为 n≤1000000 的数组。
有一个大小为 𝑘 的滑动窗口,它从数组的最左边移动到最右边。
你只能在窗口中看到 𝑘 个数字。
每次滑动窗口向右移动一个位置。
以下是一个例子:
该数组为 [1 3 -1 -3 5 3 6 7]
,𝑘 为 3。
窗口位置 | 最小值 | 最大值 |
---|---|---|
[1 3 -1] -3 5 3 6 7 | -1 | 3 |
1 [3 -1 -3] 5 3 6 7 | -3 | 3 |
1 3 [-1 -3 5] 3 6 7 | -3 | 5 |
1 3 -1 [-3 5 3] 6 7 | -3 | 5 |
1 3 -1 -3 [5 3 6] 7 | 3 | 6 |
1 3 -1 -3 5 [3 6 7] | 3 | 7 |
你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。
暴力做法
int k;
int a[N];
for (int i = 0;i<n-k+1;++i){
int min = a[i];
int max = a[i];
for (int j = i+1;j<i+k:++j){
if (a[j] <=min)
min = a[j];
if (a[j]>=max)
max = a[j];
}
cout << a[i] << a[j] << endl;
}
观察暴力做法,我们可以发现有可以优化的地方。
结合代码来说(以求最小值为例)
int hh = 0,tt = -1;
for (int i = 0;i< n;++i){
if (hh <= tt && q[hh] < i-k+1)
hh++;
while(hh <= tt && a[q[tt]]>= a[i])
tt--;
q[++tt] = i;
if (i+1>=k)
cout << a[q[hh]] << " ";
}
代码中的hh和tt可以理解为
hh代表一个指向队头元素的指针,tt代表一个指向队尾元素的指针。
而定义的两个数组a[N]和q[N],a[N]用来存储每一个元素的值,q[N]用来存储没有剔除过后的元素的下标。那么
q[tt]就可以理解为经过删除操作后的队列中队尾元素的下标,q[hh]就可以理解为队头元素的下标。
分析代码:
1.更新队头元素,防止队头元素出队列。
if (hh <= tt && q[hh] < i-k+1)
hh++;
2.比较队尾元素和要插入的值
while(hh <= tt && a[q[tt]]>= a[i])
tt--;
画张图来看
tt--;
q[++tt] = i;
认为此时tt是指向队尾元素的指针,指针自减,表示删除此时的队尾元素,而后q[++tt] = i,为队尾元素的下标重新赋值,相当于更改了队尾元素的下标,画张图更好理解
3.判断队列是否完全进入数组 ,输出最小值
if (i+1>k)
cout<< a[q[hh]] << " ";
上图
同时在q[N]数组中存储着剔除后的数组元素的下标 ,而这些下标在数组a[N]中对应的元素应该呈单调递增的趋势,因此对于每一个队列,只要输出a[q[hh]],输出队头元素,就是这个队列的最小值。