leetcode刷题记录-2104. 子数组范围和

230 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

前言

今天的题目为中等题,题目只想着要接出来的话算是一道简单题,但是按照题目要求的进阶做法来做的话就算是一个有难度的题了,本文介绍的进阶解法理解的也不是很完全,只能说是个人理解,具体的不了解的伙伴可以去看leetcode上大佬们的讲解。

每日一题

今天的每日一题 2104. 子数组范围和,难度为中等

  • 给你一个整数数组 nums 。nums 中,子数组的 范围 是子数组中最大元素和最小元素的差值。

  • 返回 nums 中 所有 子数组范围的 和 。

  • 子数组是数组中一个连续 非空 的元素序列。

 

示例 1:

输入:nums = [1,2,3]
输出:4
解释:nums 的 6 个子数组如下所示:
[1],范围 = 最大 - 最小 = 1 - 1 = 0 
[2],范围 = 2 - 2 = 0
[3],范围 = 3 - 3 = 0
[1,2],范围 = 2 - 1 = 1
[2,3],范围 = 3 - 2 = 1
[1,2,3],范围 = 3 - 1 = 2
所有范围的和是 0 + 0 + 0 + 1 + 1 + 2 = 4

示例 2:

输入:nums = [1,3,3]
输出:4
解释:nums 的 6 个子数组如下所示:
[1],范围 = 最大 - 最小 = 1 - 1 = 0
[3],范围 = 3 - 3 = 0
[3],范围 = 3 - 3 = 0
[1,3],范围 = 3 - 1 = 2
[3,3],范围 = 3 - 3 = 0
[1,3,3],范围 = 3 - 1 = 2
所有范围的和是 0 + 0 + 0 + 2 + 0 + 2 = 4

示例 3:

输入:nums = [4,-2,-3,4,1]
输出:59
解释:nums 中所有子数组范围的和是 59

 

提示:

  • 1 <= nums.length <= 1000
  • 109 <= nums[i] <= 109  

进阶:你可以设计一种时间复杂度为 O(n) 的解决方案吗?

题解

模拟

首先我们先来用模拟解题,思路很简单,用一个双重循环遍历,得到每一个子数组,然后子数组去计算最大最小值:

/**
 * @param {number[]} nums
 * @return {number}
 */
var subArrayRanges = function (nums) {
  let arr = [];
  let n = nums.length;
  let ans = 0;
  for (let i = 0; i < n; i++) {
    for (let j = i; j < n; j++) {
      arr = nums.slice(i, j + 1);
      let max = -Number.MAX_VALUE;
      let min = Number.MAX_VALUE;
      arr.forEach((elm) => {
        if (max < elm) {
          max = elm;
        }
        if (min > elm) {
          min = elm;
        }
      });
      ans = ans + max - min;
    }
  }
  return ans;
};

但是尴尬的来了,竟然超时了,那么就开始想一起要怎么去优化。 image.png

我们没有必要新建一个数组,在外层循环我们定义好了子数组的开头,内层循环的遍历其实就是在便利子数组,所有我们能直接在内层循环去判断最大最小值,并且在外层循环的时候去定义最大最小的变量

image.png

/**
 * @param {number[]} nums
 * @return {number}
 */
var subArrayRanges = function (nums) {
  let n = nums.length;
  let ans = 0;
  for (let i = 0; i < n; i++) {
    let min = Number.MAX_VALUE
    let max = -Number.MAX_VALUE;
    for (let j = i; j < n; j++) {
      min = Math.min(min, nums[j]);
      max = Math.max(max, nums[j]);
      ans = ans + max - min;
    }
  }
  return ans;
};

image.png

单调栈

这道题还有一个进阶解法,用 O(n) 的时间复杂度去解题。

结合题目最后的要求,我们要用每一个子数组中的最大值减去最小值,那么能不能换一种思路,我们碰到一个元素,去求它成为了几个子数组的最大值,或者它成为了几个子数组的最小值。因为最后每一个数组都是 最大减去最小 也就是 a - b ,我们只要求出一个数,它能成为几次 a 那么最后的答案就要加上几个 a,或者求出一个数它能成为几次 b,那么最后的答案就要减去几个 b

所以现在我们现在拿遍历到的 num[i] 出来做一个讲解,我们可以去定义,在 num[i] 左边大于它的那个数下标为left,那么从 left 到 i 这个范围中,num[i] 都是最大的,然后我们同理往右边再去定义大于等于它的数下标为 right,那么 从 i 到 right 这个范围中,num[i] 都是最大的。

接着,我们要在这个范围中去定义我们的子数组,假如子数组是从 l 下标 一直到 r 下标,那么是不是就能够得到两组组关系式:

  • left < l <=i
  • i <= r < right

我们从一张图来更直观的感受一下这个关系:

image.png

那么我们就能够得到在这个范围当中,我们能够定义出 (i-left)*(right-i) 个子数组来,并且我们知道它的权重为 num[i] 然后出现了 (i-left)*(right-i) 次,那么它为最后答案贡献的正数就是:

  • (i-left)*(right-i)*num[i]

负数方面也是同样的道理。

/**
 * @param {number[]} nums
 * @return {number}
 */
var subArrayRanges = function(nums) {
    const n = nums.length
    let arr = new Array(), ans = 0n
    for(let i = 0; i <= n; i++) {
        while(arr.length > 0 && (i == n || nums[arr[arr.length - 1]] < nums[i])) {
            const j = BigInt(arr.pop())
            ans += BigInt(nums[j]) * (BigInt(i) - j) * (j - (arr.length > 0 ? BigInt(arr[arr.length - 1]) : -1n))
        }
        arr.push(i)
    }
    arr = new Array()
    for(let i = 0; i <= n; i++) {
        while(arr.length > 0 && (i == n || nums[arr[arr.length - 1]] > nums[i])) {
            const j = BigInt(arr.pop())
            ans -= BigInt(nums[j]) * (BigInt(i) - j) * (j - (arr.length > 0 ? BigInt(arr[arr.length - 1]) : -1n))
        }
        arr.push(i)
    }
    return ans
};

image.png