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的 下一个更大元素 是指x在nums2中对应位置 右侧 的 第一个 比x大的元素。给你两个 没有重复元素 的数组
nums1和nums2,下标从 0 开始计数,其中nums1是nums2的子集。对于每个
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
给定一个循环数组
nums(nums[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,弹出0,res[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,弹出0,res[0]=2(重复赋值);2 > 1,弹出2,res[2]=2;2 < 2,压入1。栈:[1, 1]- 结束。
res[1]保持为-1。最终结果[2, -1, 2]。
3. 复杂度分析
- 时间复杂度:O(N)。虽然是 2n 的循环,但每个元素最多被压入栈一次,弹出栈一次,所以均摊下来每个元素的操作是常数级别的。
- 空间复杂度:O(N)。结果数组占 O(N),单调栈最坏情况下(严格递减数组)会存入 2n 个索引,也是 O(N) 级别。