第十章 单调栈part01
739. 每日温度
今天正式开始单调栈,这是单调栈一篇扫盲题目,也是经典题。
大家可以读题,思考暴力的解法,然后在看单调栈的解法。 就能感受出单调栈的巧妙
programmercarl.com/0739.%E6%AF…
问题描述
给定一个数组 temperatures,表示每日的温度,要求输出一个数组 res,数组中的每个元素表示距离当前天之后温度会更高的天数。如果之后没有更高的温度,输出 0。
代码解读
- 使用一个栈
stack来存储索引,用于追踪还未找到更高温度的天数。 - 初始化结果数组
res,存储每个天数的结果。
代码逻辑
-
遍历温度数组:
- 每次遇到比栈顶元素(上一个未处理的温度索引)的温度更高时,说明当前天数比栈顶对应的温度高,计算天数差,并将栈顶元素出栈。
- 然后将结果加入到
res数组中。
-
栈的作用:
- 栈中存储的是索引,用于查找当前温度与栈顶温度的天数差。
代码:
/**
* @param {number[]} temperatures
* @return {number[]}
*/
var dailyTemperatures = function(temperatures) {
let stack = []; // 栈中存储索引
let res = new Array(temperatures.length).fill(0); // 结果数组,初始化为0
for (let i = 0; i < temperatures.length; i++) {
// 如果当前温度比栈顶元素对应的温度高
while (stack.length && temperatures[i] > temperatures[stack[stack.length - 1]]) {
let prevIndex = stack.pop(); // 获取栈顶元素的索引
res[prevIndex] = i - prevIndex; // 计算等待的天数差
}
stack.push(i); // 当前索引入栈
}
return res;
};
示例
假设 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],则输出结果为 [1, 1, 4, 2, 1, 1, 0, 0]。
解释:
- 对于第 1 天(73 度),第 2 天的温度更高,所以需要等 1 天。
- 对于第 3 天(75 度),需要等 4 天才能等到 76 度。
总结
通过栈的机制,我们能高效地计算出每一天之后更高温度的天数差。
496.下一个更大元素 I
本题和 739. 每日温度 看似差不多,其实 有加了点难度。
programmercarl.com/0496.%E4%B8…
代码思路
-
栈的作用:
- 使用栈来帮助追踪
nums2中的元素,栈中存储的是nums2中元素的索引。 - 每当遇到一个比栈顶元素大的元素时,意味着这个元素是栈顶元素的“下一个更大元素”,因此将栈顶元素出栈并记录其结果。
- 使用栈来帮助追踪
-
映射关系:
- 使用一个
map来保存nums1中每个元素在结果数组res中对应的索引位置。这样当找到nums2中的下一个更大元素时,可以直接通过map查找到它在nums1中的位置。
- 使用一个
-
步骤解析:
- 先用
map将nums1中的元素与它在res数组中的位置关联起来。 - 遍历
nums2,如果栈不为空且当前元素大于栈顶元素,就说明找到了栈顶元素的下一个更大元素。通过map更新res中的值。 - 遍历完成后,栈中剩下的元素没有更大的元素,因此对应的结果依然为
-1(初始化时已经填充)。
- 先用
代码实现
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number[]}
*/
var nextGreaterElement = function (nums1, nums2) {
let stack = [];
let res = Array(nums1.length).fill(-1); // 结果数组,初始化为 -1
let map = new Map(); // 用于映射 nums1 中的元素及其对应的索引
// 将 nums1 中的元素与其索引位置对应
for (let i = 0; i < nums1.length; i++) {
map.set(nums1[i], i);
}
// 遍历 nums2,使用单调栈寻找下一个更大的元素
for (let i = 0; i < nums2.length; i++) {
// 栈中存储的是索引,如果当前元素比栈顶元素大
while (stack.length && nums2[i] > nums2[stack[stack.length - 1]]) {
let topIndex = stack.pop(); // 获取栈顶索引
if (map.has(nums2[topIndex])) {
let index = map.get(nums2[topIndex]); // 获取该元素在 nums1 中的索引
res[index] = nums2[i]; // 更新结果数组
}
}
// 将当前元素的索引入栈
stack.push(i);
}
return res;
};
示例:
let nums1 = [4, 1, 2];
let nums2 = [1, 3, 4, 2];
console.log(nextGreaterElement(nums1, nums2));
// 输出: [-1, 3, -1]
解释:
nums1[0] = 4在nums2中的下一个更大元素不存在,结果为-1。nums1[1] = 1在nums2中的下一个更大元素是3。nums1[2] = 2在nums2中的下一个更大元素不存在,结果为-1。
总结
- 使用栈可以高效地解决类似“下一个更大元素”的问题,通过栈顶元素与当前元素的比较,来寻找目标值。
map将nums1和res进行索引关联,便于快速找到并更新答案。
503.下一个更大元素II
这道题和 739. 每日温度 几乎如出一辙,可以自己尝试做一做
programmercarl.com/0503.%E4%B8…
思路:
我们希望通过单调栈来追踪元素的下一个更大元素。如果当前元素 nums[i] 大于栈顶元素,则说明我们找到了栈顶元素的下一个更大元素。栈中存储的是索引而非值,以便在更新结果数组时可以找到对应位置。
由于这是一个循环数组问题,我们可以通过模拟环形遍历,即遍历两次数组(或通过 i % nums.length 计算当前的索引),来找到每个元素的下一个更大元素。
代码:
/**
* @param {number[]} nums
* @return {number[]}
*/
var nextGreaterElements = function(nums) {
let res = Array(nums.length).fill(-1); // 结果数组,初始化为 -1
let stack = []; // 栈中存储元素索引
// 我们遍历两次数组,通过 i % nums.length 实现环形遍历
for(let i = 0; i < nums.length * 2; i++) {
let currentIndex = i % nums.length;
// 如果当前元素大于栈顶元素,则更新栈顶元素的下一个更大元素
while (stack.length && nums[currentIndex] > nums[stack[stack.length - 1]]) {
let index = stack.pop();
res[index] = nums[currentIndex];
}
// 只在第一轮将索引压入栈(第二轮不压栈,防止重复处理)
if (i < nums.length) {
stack.push(currentIndex);
}
}
return res;
};
逻辑解析:
- 环形遍历:我们遍历两次数组,通过
i % nums.length来获取当前元素的实际索引,实现“环形”的效果。 - 栈的使用:栈中存储的是数组的索引。每当我们找到一个比栈顶元素大的元素时,我们就可以确定栈顶元素的下一个更大元素。
- 结果数组:初始化为
-1,对于那些没有更大元素的数字,结果仍然为-1。
示例:
let nums = [1, 2, 1];
console.log(nextGreaterElements(nums));
// 输出: [2, -1, 2]
解释:
nums[0] = 1的下一个更大元素是2。nums[1] = 2没有更大元素,结果为-1。nums[2] = 1的下一个更大元素是2(由于这是环形数组,所以回到了第一个元素之后的值)。
总结:
通过双循环的方式,我们可以有效地解决环形数组的“下一个更大元素”问题。