DAY46

61 阅读6分钟

第十章 单调栈part01

739. 每日温度

今天正式开始单调栈,这是单调栈一篇扫盲题目,也是经典题。

大家可以读题,思考暴力的解法,然后在看单调栈的解法。 就能感受出单调栈的巧妙

programmercarl.com/0739.%E6%AF…

问题描述

给定一个数组 temperatures,表示每日的温度,要求输出一个数组 res,数组中的每个元素表示距离当前天之后温度会更高的天数。如果之后没有更高的温度,输出 0。

代码解读

  • 使用一个栈 stack 来存储索引,用于追踪还未找到更高温度的天数。
  • 初始化结果数组 res,存储每个天数的结果。

代码逻辑

  1. 遍历温度数组

    • 每次遇到比栈顶元素(上一个未处理的温度索引)的温度更高时,说明当前天数比栈顶对应的温度高,计算天数差,并将栈顶元素出栈。
    • 然后将结果加入到 res 数组中。
  2. 栈的作用

    • 栈中存储的是索引,用于查找当前温度与栈顶温度的天数差。

代码:

/**
 * @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. 对于第 1 天(73 度),第 2 天的温度更高,所以需要等 1 天。
  2. 对于第 3 天(75 度),需要等 4 天才能等到 76 度。

总结

通过栈的机制,我们能高效地计算出每一天之后更高温度的天数差。

496.下一个更大元素 I

本题和 739. 每日温度 看似差不多,其实 有加了点难度。

programmercarl.com/0496.%E4%B8…

代码思路

  1. 栈的作用

    • 使用栈来帮助追踪 nums2 中的元素,栈中存储的是 nums2 中元素的索引。
    • 每当遇到一个比栈顶元素大的元素时,意味着这个元素是栈顶元素的“下一个更大元素”,因此将栈顶元素出栈并记录其结果。
  2. 映射关系

    • 使用一个 map 来保存 nums1 中每个元素在结果数组 res 中对应的索引位置。这样当找到 nums2 中的下一个更大元素时,可以直接通过 map 查找到它在 nums1 中的位置。
  3. 步骤解析

    • 先用 mapnums1 中的元素与它在 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] = 4nums2 中的下一个更大元素不存在,结果为 -1
  • nums1[1] = 1nums2 中的下一个更大元素是 3
  • nums1[2] = 2nums2 中的下一个更大元素不存在,结果为 -1

总结

  • 使用栈可以高效地解决类似“下一个更大元素”的问题,通过栈顶元素与当前元素的比较,来寻找目标值。
  • mapnums1res 进行索引关联,便于快速找到并更新答案。

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;
};

逻辑解析:

  1. 环形遍历:我们遍历两次数组,通过 i % nums.length 来获取当前元素的实际索引,实现“环形”的效果。
  2. 栈的使用:栈中存储的是数组的索引。每当我们找到一个比栈顶元素大的元素时,我们就可以确定栈顶元素的下一个更大元素。
  3. 结果数组:初始化为 -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(由于这是环形数组,所以回到了第一个元素之后的值)。

总结:

通过双循环的方式,我们可以有效地解决环形数组的“下一个更大元素”问题。