携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第9天,点击查看活动详情
单调栈就是针对求下一个更大、小的元素的题目的算法,可以参考leedcode503. 下一个更大元素 II 这道题
503. 下一个更大元素 II
在[6,5,4,3,8]中,对于前面的 [6,5,4,3] 等数字都需要向后遍历,当寻找到元素 8 时才找到了比自己大的元素;而如果已知元素 6 向后找到元素 8 才找到了比自己的大的数字,那么对于元素 [5,4,3] 来说,它们都比元素 6 更小,所以比它们更大的元素一定是元素 8,不需要单独遍历对 [5,4,3] 向后遍历一次!
在单调栈中,遍历数组,栈中因为空先存入6(存的是下标0),而5,4,3因为比6小也依次存入【说明当前元素的「下一个更大元素」与栈顶元素相同】,遍历到8时,8比栈顶3大,那么元素3他的下一个更大元素就是8【说明当前元素是前面一些元素的「下一个更大元素」】,把3下标元素改为8.这就是核心。8继续和栈顶4比,依次类推。
因为8也要找比它大的,所以数组要循环两次。
暴力
每个元素往后找比自己大的数,数组多长就往后找几次,越界了可以
1 拼长度 nums = nums.concat(nums)
2 取余 nums[(i+item)%len]
var nextGreaterElements = function(nums) {
let len = nums.length,res = []
nums = nums.concat(nums)
for(let i=0;i<len;i++){
let time = 0,better = -1
while(time<len-1){
time++
// 或者nums[(i+item)%len]
if(nums[i+time]>nums[i]){
better = nums[i+time]
break
}
}
res.push(better)
}
return res
};
时间复杂度 o(n^2)
单调栈
// 单调栈,遍历nums循环两次成环,栈内存的是下标,遍历到比栈顶大的数时,这个数就是比栈顶指向元素更大的一个数
// 弹出栈顶,将栈顶指向的坐标元素 改为 当前数
let stack = [],len = nums.length
let res = new Array(len).fill(-1)
for(let i=0;i<len*2;i++){
let num = nums[i%len]
// 持续和栈顶比较,大的话弹出
while(stack.length&&num>nums[stack.at(-1)]){
res[stack.pop()] = num
}
stack.push(i%len)
}
return res
宫水三叶的说法
对于「找最近一个比当前值大/小」的问题,都可以使用单调栈来解决。
单调栈就是在栈的基础上维护一个栈内元素单调。
在理解单调栈之前,我们先回想一下「朴素解法」是如何解决这个问题的。
对于每个数而言,我们需要遍历其右边的数,直到找到比自身大的数,这是一个 O(n^2)的做法。
之所以是 O(n^2),是因为每次找下一个最大值,我们是通过「主动」遍历来实现的。
而如果使用的是单调栈的话,可以做到 O(n)O(n) 的复杂度,我们将当前还没得到答案的下标暂存于栈内,从而实现「被动」更新答案。
也就是说,栈内存放的永远是还没更新答案的下标。
具体的做法是:
每次将当前遍历到的下标存入栈内,将当前下标存入栈内前,检查一下当前值是否能够作为栈内位置的答案(即成为栈内位置的「下一个更大的元素」),如果可以,则将栈内下标弹出。
如此一来,我们便实现了「被动」更新答案,同时由于我们的弹栈和出栈逻辑,决定了我们整个过程中栈内元素单调。
还有一些编码细节,由于我们要找每一个元素的下一个更大的值,因此我们需要对原数组遍历两次,对遍历下标进行取余转换。
以及因为栈内存放的是还没更新答案的下标,可能会有位置会一直留在栈内(最大值的位置),因此我们要在处理前预设答案为 -1。而从实现那些没有下一个更大元素(不出栈)的位置的答案是 -1。
作者:AC_OIer 链接:leetcode.cn/problems/ne… 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
496. 下一个更大元素 I
var nextGreaterElement = function(nums1, nums2) {
// 单调栈,数组2制表,用map存,比arr好
// 参考:https://leetcode.cn/problems/next-greater-element-i/solution/xia-yi-ge-geng-da-yuan-su-i-by-leetcode-bfcoj/
let map = new Map(),stack = []
for(let i =0;i<nums2.length;i++){
let num = nums2[i]
// 这里不存索引,改存值
while(stack.length&&stack.at(-1)<num){
let p = stack.pop()
// 存储弹出的栈顶 和 它的下一个最大数
map.set(p,num)
}
stack.push(num)
}
// 没被弹出的就是找不到,返回-1
return nums1.map(i=>map.get(i)||-1)
};
739. 每日温度
var dailyTemperatures = function(temperatures) {
// 存索引,弹栈时,要存的结果是 当前索引-栈顶索引
let stack = [],len = temperatures.length,res = new Array(len).fill(0)
for(let i=0;i<len;i++){
let num = temperatures[i]
while(stack.length&&temperatures[stack.at(-1)]<num){
let p = stack.pop()
res[p] = i-p
}
stack.push(i)
}
return res
};
84. 柱状图中最大的矩形
/**
* @param {number[]} heights
* @return {number}
*/
var largestRectangleArea = function (heights) {
// 1 以前可以,现在超时了
// 原理是 遍历元素,以当前元素为最高,找左边和右边,算出面积,
let area = 0;
for (let i = 0; i < heights.length; i++) {
const height = heights[i];
let left = i;
let right = i;
// 向前遍历查找左边界:
// 判定边界的条件是遇到下一个高度大于等于当前高度。则表明当前left为左边界
// 如果说直接判断heights[left],会出现退出循环时,left多走了一步,造成计算宽度时很难处理,因此判断left - 1
// 如果left为0,heights[-1]为undefined,heights[-1]>=height都为false
while (heights[left - 1] >= height) {
left--;
}
// 向前遍历查找右边界:
// 判定边界的条件是遇到下一个高度大于等于当前高度。则表明当前right为右边界
// 如果说直接判断heights[right],会出现退出循环时,right多走了一步,造成计算宽度时很难处理,因此判断right + 1
// 如果right为heights.length,heights[heights.length]为undefined,heights[heights.length]>=height都为false
while (heights[right + 1] >= height) {
right++;
}
// 根据当前查找的边界计算宽度
const width = right - left + 1;
// 计算当前面积,并与已存储的面积对比,取最大值
area = Math.max(area, width * height);
}
return area;
// 2 、 单调栈,可以看成是上面的优化版
// 参考 https://leetcode.cn/problems/largest-rectangle-in-histogram/solution/wo-yong-qiao-miao-de-bi-yu-jiang-dan-diao-zhan-jie/
// 单调递减栈,找出下一个更小的元素,算是求出右边界,
// 左边界则是栈顶前一个元素,因为栈是递增,
// 如果矩形是 [1,2,3] 那永远不出栈,所以后面补个0
// 前面补0,是为了左边界的计算,栈无元素时,拿不到左边界,所以补0代替
heights = [0,...heights,0]
let stack = [],len = heights.length,max = 0
for(let i=0;i<len;i++){
let height = heights[i]
while(stack.length&&heights[stack.at(-1)]>height){
let p = stack.pop()
// let area = heights[p]*(i-p)
let area = heights[p]*(i-stack.at(-1)-1)
max = Math.max(max,area)
}
stack.push(i)
}
return max
};
其他题
铭记
求下一个更大、小的元素 单调栈!!
模板:
for(let i=0;i<len;i++){
let num = nums[i]
// 持续和栈顶比较,大的话弹出
while(stack.length&&num>nums[stack.at(-1)]){
res[stack.pop()] = num
}
stack.push(i)
}