作为算法中最基础且经典的一个先进后出的数据结构,栈往往是很多题目的解题关键。
目录
单调栈
聊一聊栈
一、栈的结构
二、栈的使用模板
1.引入模版
2.困难题
总结
栈是一种典型的(FILO, First in last out)结构,好比往井里丢的石头一样,最先入栈的数据沉底,最后入栈的数据在最上方。
这里只需要知道栈的使用方法即可,栈的实现非常的简单,可以用一个数组和一个index索引取模拟实现.
代码如下(示例):
而实际上,用List实现栈是更方便的。
比如在C++中#include
java中的Deque (Stack被废弃后建议使用deque,效率更高更安全)
public class MyStack{
// 用于储存元素
private T[] element;
// 永远指向最后加入的元素
private int index;
void push(T object){
if(index==element.length){
throw new Exception("stack is full");
return;
}
element[index] = object;
index++;
}
T pop(){
if(element.length==0){
return null;
}
int res = element[index];
index--;
return res;
}
T peek(){
return element[index];
}
}
我们首先来看一到leetcode的简单题,使用单调栈来进行解答.
下一个更大元素I : next-greater-element-i
题目:
给两个数组num1,num2. 其中num1是num2的子集,对每一个num1的元素找出满足 nums1[i] == nums2[j]
的下标 j
,并且在 nums2
确定 nums2[j]
的 下一个更大元素,作为查询答案放回答案数组中,如果没有更大元素返回-1
nums1 = [4,1,2], nums2 = [1,3,4,2].
res = [-1,3,-1].
对数字1, 找到nums2 = [1,3,4,2]。下一个更大元素是 3 ,其他两个数字没有更大元素.
解题思路与代码:
1. 我们已经知道num1是num2的子集,那么所有的num1元素都可以在num2中找到对应
2. 那么我们只需要知道num2中每一个元素i的“下一个更大值” j,
装在一个Map字典中. key: i, value : j
3. 对数组1的每一个值找到它的字典对应值,那么就得到了答案.
// 所以重点就是,我们如何找到num2中的下一个更大值?
public Map nextGreaterForArray(int[] num2){
/** 首先从边界开始思考:最右边的元素已经没有下一个值了,它的对应value必定为-1
于是我们开始从右往左循环
*/
Deque stack = new LinkedList<>();
Map map = new HashMap<>();
// 我们每一次都把当前元素入栈,下一次循环时,把当前元素和上一个栈内的元素对比
for(int i = num2.length-1; i>=0; i--){
// 如果我当前元素比栈内元素大,则把不需要的“矮个子”弹出
while(!stack.isEmpty() && num2[i]>=stack.peek()){
stakc.pop();
}
// 如果一直弹出到没有元素了,说明当前元素就是最大值,higher=-1,否则就为栈顶元素
int higher = stack.isEmpty()? -1:stack.peek();
// 记录与入栈
map.put(num2[i],higher);
stack.push(num2[i]);
}
return map;
}
可以看到单调栈最主要的模板是for循环里面嵌套了一个while循环,用于弹出我们不要的值。然后对栈进行空值判定和入栈,当这个Map字典拿到后,这道题也就迎刃而解了。
2334元素值大于变化阈值的子数组
题设:
给你一个整数数组 nums 和一个整数 threshold 。
找到长度为 k 的 nums 子数组,满足数组中 每个 元素都 大于 threshold / k 。
请你返回满足要求的 任意 子数组的 大小 。如果没有这样的子数组,返回 -1 。
子数组 是数组中一段连续非空的元素序列。
示例 1:
输入:nums = [1,3,4,3,1], threshold = 6
输出:3
解释:子数组 [3,4,3] 大小为 3 ,每个元素都大于 6 / 3 = 2 。
注意这是唯一合法的子数组。
解题思路和代码:
困难题一般是多个套路一起用才能解题。
第一眼从竞赛上看到这个题的时候愿意为是DFS+记忆化搜索
后来才发现是贪心+单调栈
贪心:一段区间内每个元素都大于threshould/k,在循环遍历时,如果假设我们当前取的元素就为区间的最小值,往两边去查找边界。那么区间长度越长,k越大,threshould/k则越小
如何去查询边界?单调栈.
(这不就成了去查找下一个更小元素和上一个更小元素吗)
public int validSubarraySize(int[] nums, int threshold) {
Deque stack = new LinkedList<>();
int n = nums.length;
// left,right数组记录每个元素的左区间和右区间
int[] left = new int[n];
int[] right = new int[n];
Arrays.fill(left,-1);
Arrays.fill(right,n);
// 查找每个元素左边更小值,模板来了! for+while弹出不要的元素(更大的)+判空记录+入栈
for(int i=0;i=0;i--){
while(!stack.isEmpty() && nums[i]<=nums[stack.peek()]){
stack.pop();
}
if(!stack.isEmpty()){
right[i] = stack.peek();
}
stack.push(i);
}
// 对于每一个元素形成的区间来进行最后判定,如果有一个满足了条件,返回它
for(int i =0;i(double)threshold) return k;
}
return -1;
}
以上就是单调栈的使用模板,For+while(弹出不要的元素!)+if(stack.isEmpty()) +stack.push 。
你学废了吗!