单调栈和单调队列总结

总结下面经中常考的栈和队列题目,仅供个人日后复习。

用两个栈实现队列
用两个队列实现栈
根据栈的压入序列能不能得到给定的弹出序列
包含min函数的栈
以上题目告诉我们:要适当构建辅助栈

(一)单调栈

另外,还有一种单调栈的简单数据结构。
单调栈实际上就是栈,只是利用了一些巧妙的逻辑,使得每次新元素入栈后,栈内的元素都保持有序(单调递增或单调递减)。单调栈用途不太广泛,只处理一种典型问题,即next greater element。

  • Leetcode 496:下一个更大元素 I

题目描述:
给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。
输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]。
1 <= nums1.length <= nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 104
nums1和nums2中所有整数 互不相同
nums1 中的所有整数同样出现在 nums2 中

vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
        vector<int> res;
        unordered_map<int,int> hashmap;
        stack<int> st;
        // 倒着往栈里放
        for(int i = nums2.size()-1;i>=0;i--){
            while(!st.empty() and st.top()<= nums2[i]){
                st.pop();
            }
            int value = st.empty()?-1:st.top();
            hashmap.insert({nums2[i],value});
            st.push(nums2[i]);
        }
        for(int j = 0;j<nums1.size();j++){
            res.push_back(hashmap[nums1[j]]);
        }
        return res;
  • Leetcode503 下一个更大元素 II

题目描述
给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,
这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。

本题和上一题加大了点难度就是这个数组是可以循环使用的。使用取模来模拟循环过程。原理是一样的。

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

题目描述
给你一个正整数 n ,请你找出符合条件的最小整数,其由重新排列 n 中存在的每位数字组成,并且其值大于 n 。如果不存在这样的正整数,则返回 -1 。
注意 ,返回的整数应当是一个 32 位整数 ,如果存在满足题意的答案,但不是 32 位整数 ,同样返回 -1 。

使用单调栈,从后向前找到第一对满足num[i-1] < num[i]的数,再从i处向右找到恰好大于num[i-1]的那个数num[j],交换二者的位置,再从小到大排列i+1到最末位的数组。

int nextGreaterElement(int n) {
        long long res=0;
        stack<char> char_stack;
        string str=to_string(n);
        for(int i=str.length()-1;i>=0;i--)
        {
            if(char_stack.empty()||str[i]>=char_stack.top())
                char_stack.push(str[i]);
            else
            {
                int j=0;
                while(!char_stack.empty()&&str[i]<char_stack.top())
                {
                    char_stack.pop();
                    j++;
                }
                swap(str[i],str[i+j]);
                sort(str.begin()+i+1,str.end());
                res=stoll(str);
                if(res>INT_MAX)
                    return -1;
                else
                    return res;
            }
        }
        return -1;

}

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水

首先是暴力解法,对于每个位置找到左边的最高柱子,找到右边的最高柱子,两者取其低,再减去该位置上的柱子,即为该位置上的贮水量。时间复杂度为O( n 2 n^2 n2)空间复杂度为O(1)。这在leetcode上会超出时间限制。

def trap(self, height: List[int]) -> int:
        ans = 0
        for i in range(len(height)):
            max_left =max_right = 0
            for j in range(i,-1,-1):
                max_left= max(max_left,height[j])
            for k in range(i,len(height)):
                max_right = max(max_right,height[k])
            ans += min(max_left,max_right) - height[i]
        return ans

可以把每个位置的左边最大高度和右边最大高度分别存到两个数组里,这样就避免了嵌套循环。时间复杂度为O( n n n)空间复杂度为O( n n n)代码如下:

int trap(vector<int>& height) {
        if (height.empty()) return 0;
        int ans = 0;
        int size = height.size();
        vector<int> left_max(size), right_max(size);
        left_max[0] = height[0];
        for (int i = 1; i < size; i++) {
            left_max[i] = max(height[i], left_max[i - 1]);
        }
        right_max[size - 1] = height[size - 1];
        for (int i = size - 2; i >= 0; i--) {
            right_max[i] = max(height[i], right_max[i + 1]);
        }
        for (int i = 1; i < size - 1; i++) {
            ans += min(left_max[i], right_max[i]) - height[i];
        }
        return ans;

    }

还可以用单调递减栈,相当于按行求得雨水量。时间复杂度为O( n n n),空间复杂度仍为O( n n n)。


// 在遍历数组时维护一个栈。
// 如果当前的条形块小于或等于栈顶的条形块,我们将条形块的索引入栈,意思是当前的条形块被栈中的前一个条形块界定。
// 如果我们发现一个条形块长于栈顶,我们可以确定栈顶的条形块被当前条形块和栈的前一个条形块界定,因此我们可以弹出栈顶元素并且累加答案到ans中。

 int trap(vector<int>& height) {
        int ans=0;
        stack<int> st;
        for(int i=0;i<height.size();i++){
            while(!st.empty() and height[st.top()]<height[i]){
                int top = st.top();
                st.pop();
                if(st.empty()) break;
                int distance = i - st.top() - 1;
                int bounded_height = min(height[i], height[st.top()]) - height[top];
                ans += distance * bounded_height;
            }
            st.push(i);
        }
        return ans;
    }

本题的最佳解法是时间复杂度为O( n n n),空间复杂度为O(1)的双指针解法。因为不管是在暴力解法还是暴力解法的改进解法中,我们都是要找某个位置的最左边和最右边最大值中较小的那一个,不从左和从右分开计算,我们想办法一次完成遍历。

 int trap(vector<int>& height) {
        int left = 0, right = height.size() - 1;
        int ans = 0;
        int left_max = 0, right_max = 0;
        while (left < right) {
            if (height[left] < height[right]) {
                height[left] >= left_max ? (left_max = height[left]) : ans += (left_max - height[left]);
                ++left;
            }
            else {
                height[right] >= right_max ? (right_max = height[right]) : ans += (right_max - height[right]);
                --right;
            }
        }
        return ans;
    }

(二)单调队列

与单调栈对应,还有一种单调队列的数据结构,单调队列就是一个队列,只不过使用了一点巧妙的方法,使得队列中的元素单调递增(或递减)。可以解决滑动窗口的一系列问题。

解法1: 优先队列
其中时间复杂度为O(n),空间复杂度为O(k)

对于维护一个最大值,可以想到优先队列(堆)这种数据结构,其中的大根堆实时维护一系列元素中的最大值。

c++版本

#include <vector>
#include<iostream>
#include <queue>
using namespace std;
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
	priority_queue<pair<int,int>> pq;
	for(int i =0;i<k;i++){
		pq.emplace(nums[i],i);
	}
	vector<int> ans = {pq.top().first};
	for(int i = k;i<nums.size();i++){
		q.emplace(nums[i],i);
		while(pq.top().second <= i-k){
			pq.pop();
		}
		ans.push_back(q.top().first);
	}
	return ans;
}

python版本
注意python中的堆是小根堆

import heapq
def maxSlidingWindow(nums: list, k: int) ->list:
	n = len(nums)
	q = [(nums[i],i) for i in range(k)]
	heapq.heapify(q)
	ans = [-q[0][0]]
	for i in range(k,n):
		heap.heappush(q,(-nums[i],i))
		while(q[0][1]<= i-k):
			heap.heappop(q)
		ans.append(-q[0][0])
	return ans

解法2: 单调队列
其中时间复杂度为O(n),空间复杂度为O(k)

class MonotonicQueue{
private:
	deque<int> data;
public:
	void push(int n){
		while(!data.empty() and data.back()< n){
			data.pop_back();
		}
		data.push_back(n);
	}
	int max(){
		return data.front();
	}
	void pop(int n){
		if(!data.empty() and data.front() == n){
			data.pop_front();
		}
	}
};

vector<int> maxSlidingWindow(vector<int>& nums, int k) {
	int n = nums.size();
	vector<int> res;
	MonotonicQueue window;
	for(int i =0;i<n;i++){
		if(i<k-1){
			window.push(nums[i]);
		}
		else{
			window.push(nums[i]);
			res.push_back(window.max());
			window.pop(nums[i-k+1]);
		}
	}
	return res;
}

由此我们可以发现「单调队列」的核心功能为「求出数组中每一个元素其固定区间范围内的最大 / 小值」。并且由于每个元素出队、入队最多一次,因此总的时间复杂度为 O(n)。
数组实现:

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int len = nums.size(), l = 0, r = -1;
        vector<int> q(len, 0), ans;
        for (int i = 0; i < len; i++) {
            while (l <= r && nums[q[r]] < nums[i]) r--;
            q[++r] = i;
            if (i-q[l]+1 > k) l++;
            if (i >= k-1) ans.push_back(nums[q[l]]);
        }
        return ans;
    }
};

- 剑指offer 59-ii 队列的最大值

请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1

class MaxQueue {
private:
    queue<int> data;
    deque<int> assist_que;
public:
    MaxQueue() {

    }
    
    int max_value() {
        if(!data.empty() and !assist_que.empty())
            return assist_que.front();
        else 
            return -1;

    }
    
    void push_back(int value) {
        data.push(value);
        while(!assist_que.empty() and assist_que.back()<=value){
			assist_que.pop_back();
		}
		assist_que.push_back(value);

    }
    
    int pop_front() {
        if(!data.empty()){
            int top = data.front();
            data.pop();
            if(top== assist_que.front()) assist_que.pop_front();
            return top;
		}
        else return -1;
    }
};

整理参考链接:
单调队列
单调栈

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值