『力扣题解』单调栈

222 阅读2分钟

单调栈一般用于求解“右边更大的数或更小的数”一类的问题,假如是求右边更大的数,需要保持栈顶到栈底是单调递增的,如果栈顶的值小于目前要入栈的元素,则弹出。

739. 每日温度

本题是要输出一个数组ans,ans[i]表示arr[i]之后比它更大的元素的下标 - i,如果不存在更大的元素,ans[i] = 0。

举例:[73, 74, 75, 71, 69, 72, 76, 73],输出[1, 1, 4, 2, 1, 1, 0, 0]。

这题算得上是单调栈的模板了,直接上代码:

/**
 * @param {number[]} temperatures
 * @return {number[]}
 */
var dailyTemperatures = function (temperatures) {
  const len = temperatures.length;
  const singleStack = [];
  const ans = Array(len).fill(0);

  for (let i = 0; i < len; i++) {
    // 单调栈弹出元素
    while (temperatures[i] > temperatures[singleStack.at(-1)]) {
      const index = singleStack.pop();
      // 这里的操作因题而异
      ans[index] = i - index;
    }
    singleStack.push(i);
  }

  return ans;
};

496. 下一个更大元素I

本题有两个数组,nums1是nums2的子集。对于nums1中的每个数nums[i],在nums2中要先找到和他一样的数,再看后面有没有更大的,如果有,ans[i] = 这个数;如果没有,ans[i] = -1。

本题有以下要点:

  • 单调栈用于求解nums2中“比当前数大的下一个数的下标”,存放在temp数组中,注意这次存放的不是 index - i。
  • 如果遍历nums1时,每个数都要通过遍历nums2的方式去找到在nums2中的下标,无疑增加了时间复杂度,所以应该用map。
/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number[]}
 */
var nextGreaterElement = function (nums1, nums2) {
  const len1 = nums1.length;
  const len2 = nums2.length;
  const temp = Array(len2).fill(-1);
  const ans = Array(len1).fill(0);
  const singleStack = [];
  const m = new Map();

  for (let i = 0; i < len2; i++) {
    while (nums2[i] > nums2[singleStack.at(-1)]) {
      const index = singleStack.pop();
      temp[index] = i;
    }
    singleStack.push(i);
    m.set(nums2[i], i);
  }

  for (let i = 0; i < len1; i++) {
    // const index = nums2.findIndex(item => item === nums1[i])
    const index = m.get(nums1[i]);
    ans[i] = temp[index] !== -1 ? nums2[temp[index]] : -1;
  }
  return ans;
};

503. 下一个更大元素II

本题只有一个数组,ans[i]存放比nums[i]更大的下一个元素,并且数组是首尾相连。

单调栈难度不大,难点在于怎么处理首尾相连。有两种方式:

  • 把除了最后一个元素的数组 拼接到原数组后面。
  • 模运算。
var nextGreaterElements = function (nums) {
  const _nums = nums.concat(nums.slice(0, nums.length - 1));
  const ans = Array(nums.length).fill(-1);
  const len = _nums.length;
  const singleStack = [];

  for (let i = 0; i < len; i++) {
    while (_nums[singleStack.at(-1)] < _nums[i]) {
      const index = singleStack.pop();
      ans[index] = _nums[i];
    }
    singleStack.push(i);
  }
  return ans.slice(0, nums.length);
};

我一开始就知道要用模,但是怎么用,还是想了一会。

var nextGreaterElements = function (nums) {
  const len = nums.length;
  const ans = Array(len).fill(-1);
  const singleStack = [];

  for (let i = 0, j = 0; i < 2 * len - 1; i++, j = i % len) {
    while (nums[singleStack.at(-1)] < nums[j]) {
      const index = singleStack.pop();
      ans[index] = nums[j];
    }
    singleStack.push(j);
  }
  return ans;
};

42. 接雨水

双指针法,先用leftMax和rightMax分别记录i左侧和右侧最大的高度(包括自己),在i处,能积的雨水就等于 两侧高度最大值中的较小值 - 自身高度。这种做法是按列计算。

var trap = function(height) {
  let sum = 0
  const n = height.length
  const leftMax = Array(n).fill(0)
  const rightMax = Array(n).fill(0)

  leftMax[0] = height[0]
  rightMax[n - 1] = height[n - 1]

  for (let i = 1; i < n; i++) {
      leftMax[i] = Math.max(height[i], leftMax[i - 1])
  }
  for (let i = n - 2; i >= 0; i--) {
      rightMax[i] = Math.max(height[i], rightMax[i + 1])
  }
  for (let i = 0; i < n; i++) {
      sum += Math.min(leftMax[i], rightMax[i]) - height[i]
  }
  return sum  
};

单调栈法,因为找到右侧比自己大的元素说明有凹槽,栈顶到栈底保持递减,如果遇到比栈顶大的值,就出栈,并且计算雨水。

遇到栈顶元素等于height[i]的时候,可以出栈,也可以不出。出栈是更新了了此时右边界的值,举例如下:

   --       --
--    -- --
0  1  2  3  4

可见,中间有两个凹槽,能积的雨水为2,如果等于的情况出栈,则遇到i = 4时,栈里的情况是:[1, 3],h = Math.min(1, 1) - 0 = 1,w = 4 - 1 - 1 = 2,也就是当当前元素 > 栈顶元素时,while循环进行两轮。从这里也可以看出,单调栈是按行计算,当然不一定每次只算一行,如果雨水区域刚好构成了高大于1的矩形,一次也可以算多行。

如果等于的情况不出栈,则遇到i = 4时,栈是[1, 2, 3],第一次h = Math.min(1, 0) - 0 = 0, w = 1,第二次h = 1, w = 2,while循环进行了两轮。

所以等于的情况可以出栈,也可以不出。

var trap = function (height) {
  let sum = 0;
  const n = height.length;
  const singleStack = [];

  for (let i = 0; i < n; i++) {
    while (height[singleStack.at(-1)] < height[i]) {
      const index = singleStack.pop();
      // 左边界还需要拿此时的栈顶,因此要判断栈不为空
      if (singleStack.length > 0) {
        const h = Math.min(height[i], height[singleStack.at(-1)]) - height[index];
        const w = i - singleStack.at(-1) - 1;
        sum += h * w;
      }
    }
    singleStack.push(i);
  }

  return sum;
};