单调栈,看这一篇就够了!

本人对单调栈印象深刻,前几周去字节跳动实习,就问了这类题,由于没写出来,错失了实习的机会。。

顾名思义,单调栈实际上就是栈,只是利用了一些巧妙的逻辑,使得每次新元素入栈后,栈内的元素都保持有序(单调递增或单调递减)

首先看一个问题,没错,就是字节面试原题!!!

给你一个数组[2,1,2,4,3],返回数组[4,2,4,-1,-1]

意思就是返回原数组每个元素后第一个比它大的元素,如果没有比它大的元素,就返回-1。比如数组第一个元素2后第一个比它大的元素是4,第二个元素1后第一个比它的元素是2,而4后没有比它大的元素,因此返回-1

暴力方法很好解决,但是时间复杂度为 O ( n 2 ) O(n^2) O(n2)

因此用下面单调栈的方法来解决,首先看下代码

vector<int> nextGreaterElement(vector<int>& nums){
	vector<int> ans(nums.size());
	stack<int> s;
	for(int i = nums.size() - 1; i >= 0; i--){
		while(!s.empty() && s.top() <= nums[i]){
			s.pop();
		}
		ans[i] = s.empty() ? -1 : s.top();
		s.push(nums[i]);
	}
	return ans;	
}

这就是单调栈解决问题的模板,for循环从后往前扫描元素,如果栈中的元素比当前元素小,那么就出栈,因为当前元素把栈顶元素挡住了,因此,栈中比当前元素小的元素也没有存在的必要了

从整体看,总共有n个元素,每个元素都被push入栈了一次,而最多会被pop一次,没有任何冗余操作,所以时间复杂度为 O ( n ) O(n) O(n)

下面来个简单的变形加深一下理解

一个数组T=[73,74,75,71,69,72,76,73],数组存放的是近几天的气温,求对于每一天,至少还要等多少天才能等到一个更暖和的气温,如果等不到返回0

例如,给定数组T=[73,74,75,71,69,72,76,73],返回[1,1,4,2,1,1,0,0]

其实这个问题本质还是找Next Greater Number,只需要原本栈中的元素换为数组的索引即可

vector<int> getGreaterElements(vector<int>& nums){
    int n = nums.size();
    vector<int> res(n);
    stack<int> s;
    for(int i = 2*n-1; i >=0; i--){
        while(!s.empty() && s.top() <= nums[i%n])
            s.pop();
        res[i%n] = s.empty() ? -1 : s.top();
        s.push(nums[i%n]);
    }
    return res;
}

还有一个问题,如果数组是环形的怎么办呢?

给一个数组[2,1,2,4,3],返回数组[4,2,4,-1,4]。因为数组是环形的,所以最有一个元素3绕了一圈后找到了比自己大的元素4

我们一般通过%运算符来模拟环形数组的效果,在这里可以考虑这样的思路,将原始数组翻倍,就是将[2,1,2,4,3]变为[2,1,2,4,3,2,1,2,4,3],这样的话,每个元素不仅可以比较自己右边的元素,而且也可以和左边的元素比较

在这里,我们不必将这个翻倍的数组构造出来,而是利用循环数组的技巧来模拟,看代码

vector<int> getGreaterElements(vector<int>& nums){
    int n = nums.size();
    vector<int> res(n);
    stack<int> s;
    for(int i = 2*n-1; i >=0; i--){
        while(!s.empty() && s.top() <= nums[i%n])
            s.pop();
        res[i%n] = s.empty() ? -1 : s.top();
        s.push(nums[i%n]);
    }
    return res;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值