IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 单调栈总结 -> 正文阅读

[数据结构与算法]单调栈总结

单调栈

所谓单调栈,是指栈内的元素是单调的,即单调递增或单调递减。

单调栈一般用来求一维数组中任一元素左边或(和)右边第一个大于或小于的元素位置或距离。原理如下:

例如,求一维数组中任一元素离右边第一个大于该元素的元素的距离。nums = [73,74,75,71,69,72,76,73],用result存储结果。可以看出最终result = [1,1,4,2,1,1,0,0]。我们用单调栈来计算一下。

因为是求的右边第一个大于的元素的距离,所以从最左边的元素开始入栈,栈内存储的是元素的下标,又因为求的是大于的元素,所以遇见比当前栈顶小的元素就将该元素入栈,遇见更大的就将当前栈顶元素出栈(即维持的是一个单调递减的栈),并且存储到result数组中,存储的结果应为result[stack.top()] = i - stack.top(),即当前元素的下标减去栈顶元素的下标得到距离。重复此过程可得到正确的结果。下面看几个例子吧。

739. 每日温度

这个题其实就是上面举的例子,按上面说的做即可,注意要判断栈是否为空。

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

496. 下一个更大元素 I

这个题和上面那个题也差不多,只不过要在两个数组之间查找一下,因为没有重复元素,所以使用unordered_map。

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

503. 下一个更大元素 II

这个题其实和上面那个题差不多,只不过要循环两遍,可以直接下面这么循环

class Solution {
public:
    vector<int> nextGreaterElements(vector<int>& nums) {
        int n = nums.size();
        vector<int> result(n, -1);
        stack<int> st;
        st.push(0);
        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;
    }
};

也可以像下面这么笨拙一点循环。

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

42. 接雨水

在这里插入图片描述

我们按照列来计算,即计算每一列可以装多少雨水,观察可以发现,每一列可接的雨水量取决于min(左边最高的柱子,右边最高的柱子) - 当前柱子高度,如果结果小于等于0说明接不了雨水。

所以可以遍历每一个柱子,找出两边符合条件的柱子进行计算,时间复杂度为O(n^2),代码如下:

class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        int ans = 0;
        for (int i = 0; i < n; i++) {
            if (i == 0 || i == n - 1)
                continue;
            int left = height[i], right = height[i];
            for (int j = i - 1; j >= 0; j--) {
                if (height[j] > left)
                    left = height[j];
            }
            for (int j = i + 1; j < n; j++) {
                if (height[j] > right)
                    right = height[j];
            }
            int h = min(left, right) - height[i];
            if (h > 0)
                ans += h;
        }
        return ans;
    }
};

不过这个代码是有超时的,因为有很多重复计算,左边右边的柱子被多次计算。所以我们可以利用动态规划的思想把每次计算的结果保存下来,dp_left[i]表示的是第0到i个柱子中最高的,dp_right[i]表示的是第i到第n - 1个柱子中最高的,

dp_left[i] = max(height[i], dp_left[i - 1])
dp_right[i] = max(height[i], dp_right[i + 1])

进一步精简的话可以用两个变量直接代替数组。代码如下:

class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        int ans = 0;
        vector<int> max_left(n, 0);
        vector<int> max_right(n, 0);
        max_left[0] = height[0];
        max_right[n - 1] = height[n - 1];
        for (int i = 1; i < n; i++) {
            max_left[i] = max(max_left[i - 1], height[i]);
        }
        for (int i = n - 2; i >= 0; i--) {
            max_right[i] = max(max_right[i + 1], height[i]);
        }
        for (int i = 0; i < n; i++) {
            if (i == 0 || i == n - 1)
                continue;
            int h = min(max_left[i], max_right[i]) - height[i];
            if (h > 0)
                ans += h;
        }
        return ans;
    }
};

这样时间复杂度就降为O(n)了。

也可以用单调栈来做,单调栈里的元素从栈底到栈头(栈头压入弹出)递减的顺序,遍历过程中,如果遇到更小的元素压入栈中,如果遇见相等的元素要把原来的栈顶元素弹出再压入新的元素,这样是为了保持宽度的正确,遇到更大就可以算相应的接水量了,接水量的计算公式为长 * 宽。讲的不太清楚,具体可以看这个博客柱状图中最大的矩形。代码如下:

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

84. 柱状图中最大的矩形

这个题和上面那道题差不多,只不过一个求的是自己两侧比自己高的柱子,这个求的是自己两侧比自己低的柱子。单调栈的代码如下:

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        heights.push_back(0);
        heights.insert(heights.begin(), 0);
        stack<int> st;
        st.push(0);
        int sum = 0;
        for (int i = 1; i < heights.size(); i++) {
            while (!st.empty() && heights[i] < heights[st.top()]) {
                int mid = st.top();
                st.pop();
                int w = i - st.top() - 1;
                sum = max(sum, w * heights[mid]);
            }
            st.push(i);
        }
        return sum;
    }
};

总结

单调栈一般用于求任一元素左侧或(和)右侧第一个大于或小于的元素位置。特别要注意的是一般栈中存储的是元素的下标,所以使用的时候要转化一下。

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-07-20 19:09:52  更:2022-07-20 19:10:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 23:27:26-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码