单调栈总结

单调栈

概述

所谓单调栈,指的是栈中的值保持着单调,当有一个新值到来时,需要比较新值与栈顶元素,如果不符合单调性,需要一直弹出栈顶元素直至符合单调性或者栈空才能压入新值。单调栈一般用来求左边或右边第一个比自己大或小的元素的位置。使用单调栈主要要想清楚三种情况,当新值大于等于小于栈顶元素的情况。

题目

739.每日温度

在这里插入图片描述

这道题相当于是求下一个更高温度距离当前的距离,所以我们栈要维持一个递减的顺序,即当新值大于栈顶元素的时候需要弹出。栈中存放的是下标。这样每次新值到来我们进行比较,如果大于说明找到了当前栈顶元素的下一个更大温度,然后把距离存入result中即可。代码如下:

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        stack<int> st;
        vector<int> result(temperatures.size(), 0);
        for (int i = 0; i <temperatures.size(); ++i) {
            while (!st.empty() && temperatures[i] > temperatures[st.top()]) {
                result[st.top()] = i - st.top();
                st.pop();
            }
            st.push(i);
        }
        return result;
    }
};

496.下一个更大元素I

在这里插入图片描述

这道题虽然说是简单,但是我觉得会比上面那道题难一点,当然简单可能是因为这道题暴力解比较方便(其实上面那道题暴力也挺方便),这道题首先是要把nums1的映射关系放到map中,不能把nums2的放到map中,然后还是用单调栈,在遍历nums2的过程中使用单调栈,当栈顶元素弹出时,我们知道该元素对应的下一个更大元素找到了,此时我们只需要在map里面看一下是否有该元素,如果有就把下一个更大元素放到result中去即可。代码如下:

class Solution {
public:
    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
        unordered_map<int, int> my_map;
        for (int i = 0; i < nums1.size(); ++i) {
            my_map[nums1[i]] = i;
        }
        vector<int> result(nums1.size(), -1);
        stack<int> st;
        for (int i = 0; i < nums2.size(); ++i) {
            while (!st.empty() && nums2[i] > nums2[st.top()]) {
                int temp = nums2[st.top()];
                if (my_map.find(temp) != my_map.end()) {
                    result[my_map[temp]] = nums2[i];
                }
                st.pop();
            }
            st.push(i);
        }
        return result;
    }
};

503.下一个更大数组II

在这里插入图片描述

其实这个题是真不难,因为很明显,重复遍历两遍就能解决循环数组的问题,或者说笨拙一点的方法就是把两个数组拼接在一起遍历。巧妙一点的方法就是用取余来遍历两遍。最朴实的方法就是把代码直接写两遍。代码如下:

可以

class Solution {
public:
    vector<int> nextGreaterElements(vector<int>& nums) {
        vector<int> result(nums.size(), -1);
        stack<int> st;
        int n = nums.size();
        for (int i = 0; i < n; ++i) {
            while (!st.empty() && nums[i] > nums[st.top()]) {
                result[st.top()] = nums[i];
                st.pop();
            }
            st.push(i);
        }
        for (int i = 0; i < n; ++i) {
            while (!st.empty() && nums[i] > nums[st.top()]) {
                result[st.top()] = nums[i];
                st.pop();
            }
            st.push(i);
        }
        return result;
    }
};

也可以

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

42.接雨水
在这里插入图片描述

这道题无论是用动态规划做还是为了减小空间复杂度用双指针做,都要决定是从行来算雨水还是从列来算雨水。我选择的是从列来算雨水,即算每一列能装的雨水的量,相加即得到答案。那每一列能装的雨水的量是多少呢,是该列左边最高的列和右边最高的列中的较小值。注意,比较的时候从当前列比较起,如果最高的就是当前列,即雨水量为0嘛。所以关键的方程为 sum += min(left_max, right_max) - height[i]。在动态规划和双指针中我们最主要就是要找出min(left_max, right_max)。

动态规划

动态规划中求每个列的left_max和right_max比较简单,直接遍历就行。left_max[i] = max(cur, left_max[i - 1]),right_max[i] = max(cur, right_max[i +1]),然后再求每列的雨水量。代码如下:

class Solution {
public:
    int trap(vector<int>& height) {
        vector<int> left_max(height.size());
        vector<int> right_max(height.size());
        left_max[0] = height[0];
        for (int i = 1; i < height.size(); ++i) {
            left_max[i] = max(height[i], left_max[i - 1]);
        }
        right_max[height.size() - 1] = height[height.size() - 1];
        for (int i = height.size() - 2; i >= 0; --i) {
            right_max[i] = max(height[i], right_max[i + 1]);
        }
        int sum = 0;
        for (int i = 1; i < height.size() - 1; ++i) {
            sum += min(left_max[i], right_max[i]) - height[i];
        }
        return sum;
    }
};

双指针

动态规划的时间复杂度为O(n),空间复杂度为O(n),空间复杂度还是有点高,可以用双指针的方法降低。其实我们需要的不是left_max和right_max两个值,我们需要的是left_max和right_max里的较小值,我们按照双指针的方式进行遍历,使用left和right分别指向首位,所以我们在遍历求每列的雨水的过程中维持left_max和right_max。然后两相比较,如果left_max<right_max,说明当前left要用的就是left_max,因为虽然右边的max没有真正求出来,但是已经有一个right_max比left_max大了,所以left的当前较小值一定是left_max,所以我们这时候要求的是left列的雨水量,求玩后left向右移动;同理当right_max<left_max时移动right。代码如下:

class Solution {
public:
    int trap(vector<int>& height) {
        int left = 0, right = height.size() - 1;
        int max_left = 0, max_right = 0;
        int sum = 0;
        while (left < right) {
            max_left = max(max_left, height[left]);
            max_right = max(max_right, height[right]);
            if (max_left < max_right) {
                sum += max_left - height[left];
                ++left;
            }
            else {
                sum += max_right - height[right];
                --right;
            }
        }
        return sum;
    }
};

单调栈

其实只要形成两边高中间低的情况就可以接雨水了,即保持一个递减的序列,遇到第一个更大的元素就可以接雨水了,所以可以使用单调栈。使用单调栈考虑两个因素,一个是栈内存什么,一个是遇到大于等于小于当前栈顶元素的处理逻辑。

这里我们使用长乘宽来算面积,所以只需要存下标即可。因为我们要形成大小大的情况,所以我们应该是遇到比栈顶元素更大的元素时弹出栈顶元素,然后利用此时的(弹出后)栈顶元素与当前元素的较小值减去弹出元素的高度得到高,当前元素减去栈顶元素得到宽,即得到要求的面积;遇到当前元素等于栈顶元素可以弹出也可以不弹出,虽然处理的逻辑变了,但是不影响结果;如果是小于的话,直接压入即可。代码如下:

class Solution {
public:
    int trap(vector<int>& height) {
        stack<int> st;
        st.push(0);
        int sum = 0;
        for (int i = 1; i < height.size(); ++i) {
            while (!st.empty() && height[i] > height[st.top()]) {
                int mid = st.top();
                st.pop();
                if (!st.empty()) {
                    int h = min(height[i], height[st.top()]) - height[mid];
                    int w = i - st.top() - 1;
                    sum += h * w;
                }
            }
            st.push(i);
        }
        return sum;
    }
};

不过单调栈的时间复杂度和空间复杂度都不占优,如果最佳的话,还是双指针。

84.柱状图中最大的矩形

在这里插入图片描述

这个题和接雨水有点像但是又不完全相同,接雨水要找的是当前列左右最大的列的较小值,而这个是要找当前列左右第一个更小的值,而且是两边都需要,不是要较小值较大值什么的,这就决定了不能再用双指针法把空间复杂度降到O(1)了。

这道题的具体思想是算完整包含当前列的最大矩形,然后选出最大值,而完整包含当前列的最大矩形是当前列加上两边连续的比当前列高的那部分,比当前列低的就不能加进来了,否则当前列就不完整了,所以我们要找当前列左右两边第一个更低的列,假设为left_min和right_min,则包含完整当前列的最大矩形面积为height[i] * (right_min - left_min - 1)。

动态规划

可以使用动态规划来做,但是现在不是求最小,而是第一个较小,所以要用while循环得到每列的左右第一个较小值,然后再遍历比较得到结果。代码如下:

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        vector<int> left_min(heights.size());
        left_min[0] = -1;
        for (int i = 1; i < heights.size(); ++i) {
            int pre = i - 1;
            while (pre >= 0 && heights[pre] >= heights[i])
                pre = left_min[pre];
            left_min[i] = pre;
        }
        vector<int> right_min(heights.size());
        right_min[heights.size() - 1] = heights.size();
        for (int i = heights.size() - 2; i >= 0; --i) {
            int next = i + 1;
            while (next < heights.size() && heights[next] >= heights[i])
                next = right_min[next];
            right_min[i] = next;
        }
        int sum = 0;
        for (int i = 0; i < heights.size(); ++i) {
            int temp = (right_min[i] - left_min[i] - 1) * heights[i];
            sum = max(sum, temp);
        }
        return sum;
    }
};

说实话,动态规划的这种做法代码是不好写的,尤其是前面两个循环,还要考虑不能陷入死循环,所以还是用单调栈来做比较方便。

单调栈

单调栈还是看两个因素,一个是栈里面存的什么,一个是遇到大于等于小于的情况怎么做。这道题栈里面还是存下标。因为我们是要构造小大小的情况,所以遇到更大的元素直接压入,遇到相等的元素其实怎么做都行,我们这里就不压入了。遇到更小的元素就可以弹出栈顶元素开始计算了,因为这时弹出元素的左边第一个较小元素就是栈顶元素,右边第一个较小元素就是当前元素,所以直接算即可。代码如下:

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        stack<int> st;
        heights.push_back(0);
        heights.insert(heights.begin(), 0);
        int result = 0;
        st.push(0);
        for (int i = 1; i < heights.size(); ++i) {
            while (heights[i] < heights[st.top()]) {
                int mid = st.top();
                st.pop();
                int sum = (i - st.top() - 1) * heights[mid];
                result = max(result, sum);
            }
            st.push(i);
        }
        return result;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Python单调栈和单调队列是两种不同的数据结构单调栈是一个,它的特点是内的元素是单调的,可以是递增或递减的。在构建单调栈时,元素的插入和弹出都是在的一端进行的。与此类似,单调队列也是一个队列,它的特点是队列内的元素是单调的,可以是递增或递减的。在构建单调队列时,元素的插入是在队列的一端进行的,而弹出则是选择队列头进行的。 单调队列在解决某些问题时,能够提升效率。例如,滑动窗口最大值问题可以通过使用单调队列来解决。单调队列的结构可以通过以下代码来实现: ```python class MQueue: def __init__(self): self.queue = [] def push(self, value): while self.queue and self.queue[-1 < value: self.queue.pop(-1) self.queue.append(value) def pop(self): if self.queue: return self.queue.pop(0) ``` 上述代码定义了一个名为MQueue的类,它包含一个列表作为队列的存储结构。该类有两个方法,push和pop。push方法用于向队列插入元素,它会删除队列尾部小于插入元素的所有元素,并将插入元素添加到队列尾部。pop方法用于弹出队列的头部元素。 总结来说,单调栈和单调队列都是为了解决特定问题而设计的数据结构单调栈在构建时元素的插入和弹出都是在的一端进行的,而单调队列则是在队列的一端进行的。在Python,可以通过自定义类来实现单调队列的功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值