代码随想录算法训练营 Day41 | 单调栈 part01

739. 每日温度

给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。

cpp 复制代码
class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        int n=temperatures.size();
        // 初始化结果数组,默认0表示没有找到更高的温度
        vector<int>res(n,0);
        // 单调栈:存的是温度数组的【索引】,从栈底到栈底保持单调递增(对应温度单调递减)
        stack<int>st;
        // 遍历每一天的温度
        for(int i=0;i<n;i++){
            // 核心逻辑:如果栈不为空,且当前温度 > 栈顶索引对应的温度
            // 说明当前这一天,就是栈顶那一天"等待的更高温度"
            while(!st.empty() && temperatures[i]>temperatures[st.top()]){
                // 计算等待天数:当前天的索引 - 栈顶天的索引
                res[st.top()]=i-st.top();
                // 栈顶那一天的问题已解决,出栈
                st.pop();
            }
            // 当前天还没有找到比它更高的温度,将它的索引压入栈中,等待后续比对
            st.push(i);
        }
        // 遍历结束后,栈里剩下的索引对应的结果自然是初始值0(后面没有更高温度了)
        return res;
    }
};

总结

1. 核心思想:空间换时间,消除"无效等待"

单调栈的本质是维护一个等待队列。栈里存的是"还在苦苦等待更高温度的日子"。

当你遇到一个温度很高的日子时,它不仅能解决自己的问题,还能顺手把前面那些比它冷的日子的问题一次性全部解决(while 循环的作用),这就避免了每个日子都去往后傻傻遍历。

2. 为什么存索引不存数值?

因为题目要求返回的是距离(等了几天),必须通过 i - st.top() 算出来。如果栈里存数值,你就丢失了位置信息。如果只需要返回下一个更大元素的值(如 496题),才可以存数值。

3. 什么时候用递增栈,什么时候用递减栈?
  • 找下一个更大元素(本题):用单调递增栈(栈底到栈顶温度递减)。当前元素大于栈顶时触发 while
  • 找下一个更小元素(如 42. 接雨水):用单调递减栈。当前元素小于栈顶时触发 while

496. 下一个更大元素 I

nums1 中数字 x 的 下一个更大元素 是指 xnums2 中对应位置 右侧 的 第一个 比 x 大的元素。

给你两个 没有重复元素 的数组 nums1nums2 ,下标从 0 开始计数,其中nums1nums2 的子集。

对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j] 的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1

返回一个长度为 nums1.length 的数组ans作为答案,满足ans[i]是如上所述的 下一个更大元素 。

cpp 复制代码
class Solution {
public:
    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
        int n=nums1.size(),m=nums2.size();
        // 初始化结果数组为-1,因为题目要求找不到时返回-1
        vector<int>res(n,-1);
        stack<int>st;
        // 哈希表:建立 nums1 中数值 到 其索引 的映射,方便直接赋值
        unordered_map<int,int>ump;
        for(int i=0;i<n;i++) ump[nums1[i]]=i;
        // 遍历 nums2,使用单调栈找下一个更大元素
        for(int i=0;i<m;i++){
            // 当前元素 > 栈顶元素,触发处理逻辑
            while(!st.empty() && nums2[i]>st.top()){
                // 关键判断:如果栈顶这个数字在 nums1 里存在
                if(ump.count(st.top())){
                    // 拿到它在 nums1 中的索引,把当前更大元素直接填入结果数组
                    int index=ump[st.top()];
                    res[index]=nums2[i];
                }
                // 栈顶元素处理完毕(无论它在不在这个map里),直接出栈
                st.pop();
            }
            // 当前元素压栈(注意:这里压入的是数值,因为不需要算距离)
            st.push(nums2[i]);
        }
        return res;
    }
};

总结

1. 栈里存什么?
  • 每日温度:存索引 i。因为要算距离(i - st.top())。
  • 本题:存数值 nums2[i]。因为不需要算距离,只需要知道"下一个更大的值是谁",拿数值直接去哈希表里查更方便。
2. 多了一个哈希表 unordered_map
  • 因为题目要求只在 nums1 出现的元素里找结果。哈希表的作用是过滤:在 nums2 里触发单调栈逻辑时,如果栈顶的元素 nums1 里根本没有,直接无视, pop 掉就行;如果有,就通过 map 拿到它在 res 中的位置填上答案。
3. 默认值的变化
  • 每日温度:默认 0(等 0 天)。
  • 本题:默认 -1(题目规定的找不到时的返回值)。

503. 下一个更大元素 II

给定一个循环数组 numsnums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素

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

cpp 复制代码
class Solution {
public:
    vector<int> nextGreaterElements(vector<int>& nums) {
        int n = nums.size();
        // 初始化结果数组,默认全部填 -1(表示没有找到下一个更大的元素)
        vector<int> res(n, -1);
        // 单调递减栈(从栈顶到栈底递减),栈中存放的是元素的下标
        // 注意:栈中可能会存放重复的下标,但由于遇到更大的元素时之前的下标已经被弹出,所以不影响最终结果
        stack<int> st; 
        // 核心逻辑:循环遍历 2n 次(模拟环形数组)
        for (int i = 0; i < n * 2; i++) {
            // 当栈不为空,且当前遍历到的元素 大于 栈顶下标对应的元素时
            while (!st.empty() && nums[i % n] > nums[st.top()]) {
                // 找到了栈顶元素的下一个更大元素,记录到结果数组中
                res[st.top()] = nums[i % n];
                // 栈顶元素已经找到了答案,将其出栈,继续看下一个栈顶元素
                st.pop();
            }
            // 将当前元素的下标入栈(注意取模 n,保证下标在 0 到 n-1 之间)
            st.push(i % n);
        }
        return res;
    }
};

总结

1. 算法核心思想:单调栈 + 循环数组模拟
  • 单调栈的作用:栈中保存的是还没找到下一个更大元素的索引。并且栈内索引对应的元素值是单调递减的(从栈底到栈顶)。如果遇到一个更大的数,就会把栈里比它小的数全部"解决"掉。
  • 循环数组的模拟:最优雅的处理方式不是去写复杂的取余逻辑,而是直接把数组在逻辑上拼接两遍(长度遍历 2n),通过 i % n 来获取实际元素。这样,数组末尾的元素就可以自然而然地"看到"数组开头的元素了。
2. 图解执行过程(以 nums = [1, 2, 1] 为例)
  • i=0 (值1):栈空,压入索引 0。栈:[0]
  • i=1 (值2):2 > 1,弹出 0res[0]=2;压入 1。栈:[1]
  • i=2 (值1):1 < 2,压入 2。栈:[1, 2]
  • i=3 (值1):1 < 1,压入 0。栈:[1, 2, 0] (注:索引0再次入栈)
  • i=4 (值2):2 > 1,弹出 0res[0]=2 (重复赋值);2 > 1,弹出 2res[2]=2;2 < 2,压入 1。栈:[1, 1]
  • 结束。res[1] 保持为 -1。最终结果 [2, -1, 2]
3. 复杂度分析
  • 时间复杂度:O(N)。虽然是 2n 的循环,但每个元素最多被压入栈一次,弹出栈一次,所以均摊下来每个元素的操作是常数级别的。
  • 空间复杂度:O(N)。结果数组占 O(N),单调栈最坏情况下(严格递减数组)会存入 2n 个索引,也是 O(N) 级别。
相关推荐
嘻嘻哈哈樱桃2 小时前
牛客经典101题解题集--二分查找/排序
数据结构·算法·职场和发展
lihihi2 小时前
CF1992F Valuable Cards
算法
Omics Pro2 小时前
癌症亚型分类新型多组学整合框架
大数据·人工智能·python·算法·机器学习·分类·数据挖掘
熬夜敲代码的猫2 小时前
C++:模板精讲
c++·算法·模板
MegaDataFlowers2 小时前
3.无重复字符的最长子串
算法
人道领域2 小时前
【LeetCode刷题日记】20.有效的括号
算法·leetcode·职场和发展
生信研究猿2 小时前
#P3492.第1题-基于决策树预判资源调配优先级
python·算法·决策树
贾斯汀玛尔斯2 小时前
每天学一个算法--缓存淘汰策略(LRU / LFU · 结构与复杂度)
算法·缓存
大熊背2 小时前
一套为硬件加速设计的经典边缘检测流水线(一)----边缘细化原理
人工智能·算法·计算机视觉·梯度计算