单调栈实际上就是栈,只是利用了一些巧妙的逻辑,使得每次新元素入栈后,栈内的元素都保持有序(单调递增或单调递减)。
听起来有点像堆(heap)?不是的,单调栈用途不太广泛,只处理一种典型的问题,叫做 Next Greater Element。
一、下一个更大元素Ⅰ(简单)
下一个更大元素Ⅰ
用这个图更方便理解,从数组最后一个元素开始遍历,判断该元素与栈顶元素大小,如果栈顶小于等于当前数,出栈,继续遍历栈,直到找到第一个大于该元素的值。如果栈都为空了,说明没找到,结果-1。
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
Stack<Integer> stack = new Stack<>();
Map<Integer, Integer> map = new HashMap<>();
for (int i = nums2.length - 1; i >= 0; i--) {
// 注意一定从数组尾部开始遍历入栈
// peek是取栈顶
while (!stack.isEmpty() && stack.peek() <= nums2[i]) {
// 弹出
stack.pop();
}
if (stack.isEmpty()) {
map.put(nums2[i], -1);
} else {
map.put(nums2[i], stack.peek());
}
// 入栈
stack.push(nums2[i]);
}
for (int i = 0; i < nums1.length; i++) {
nums1[i] = map.get(nums1[i]);
}
return nums1;
}
}
二、下一个更大元素Ⅱ(中等)
改编题,加了条件:数组是可循环遍历的。
为了实现数组的循环遍历,常见的办法是再增加同样一个数组,使得数组长度为2n。也可以不增加数组,假装数组长度为2n,使用取模来实现循环遍历。
例如:1 2 3 4 5,n=5
for (int i = 0; i < 2 * n; i++) {
print(nums[i % n]);
// 当 i = n时,刚好循环到第一个元素
}
对于本题,在单调栈模板的基础上加上循环遍历即可。
class Solution {
public int[] nextGreaterElements(int[] nums) {
Stack<Integer> stack = new Stack<>();
int n = nums.length;
int[] ans = new int[n];
for (int i = 2 * n - 1; i >= 0; i--) {
// 按照单调栈模板,同样从最后一个元素开始遍历
while(!stack.isEmpty() && stack.peek() <= nums[i % n]) {
stack.pop();
}
if (stack.isEmpty()) {
ans[i % n] = -1;
} else {
ans[i % n] = stack.peek();
}
stack.push(nums[i % n]);
}
return ans;
}
}
从2 * n - 1,那就是第二个重复数组的最后一个元素开始遍历,就可以保证整个数组的元素是可以循
环遍历的。
三、每日温度
每日温度
需要注意的是,这里存储下标,不是温度。
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
Stack<Integer> stack = new Stack<>();
int[] ans = new int[n];
for (int i = n - 1; i >= 0; i--) {
while (!stack.isEmpty() && temperatures[stack.peek()] <= temperatures[i]){
stack.pop();
}
if (stack.isEmpty()) {
ans[i] = 0;
} else {
ans[i] = stack.peek() - i;
}
// 存下标!!
stack.push(i);
}
return ans;
}
}
单调找用于快速找到第一个大于该数的数,可以寻找左边也可以寻找右边,也可以循环查找。