2104. 子数组范围和(2022-3-4)
给你一个整数数组 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)
的解决方案吗?
解题思路
方法一: 暴力枚举
没什么技术含量,但是好在写起来、理解起来都很简单。只需要在一系列子数组里维护一个最大最小值就好了。
var subArrayRanges = function(nums) {
let len = nums.length
let ans = 0
for(let i = 0; i < len-1; i++){
let max=-Infinity,min=Infinity
for(let j = i; j < len; j++){
max = nums[j] > max ? nums[j] : max
min = nums[j] < min ? nums[j] : min
ans += max - min
}
}
return ans
};
方法二:单调栈、记录每个元素的作用次数
首先何为单调栈,「先进后出」可以通过数组尾部的方法push
和pop
来实现;单调递增、单调递减就是从小到大、从大到小。
究其子数组最大值减去最小值这个行为,其实就是被认为“大”的元素处于「被减数」,被认为“小”的元素处于「减数」;我们只需要知道当前元素nums[i]
当了多少次「被减数」和「减数」。那每个元素的总和就是答案了。
维护一个单调栈的目的在于,去前边寻找比当前元素小的值,先统计当前元素作为「被减数」的次数
统计「减数」时也是同样维护一个单调栈。
var subArrayRanges = function(nums) {
const n = nums.length
let stack = [], ans = 0n
for(let i = 0; i <= n; i++) {
while(stack.length > 0 && (i == n || nums[stack[stack.length - 1]] < nums[i])) {
const j = BigInt(stack.pop())
console.log(i,j)
ans += BigInt(nums[j]) * (BigInt(i) - j) *
(j - (stack.length > 0 ? BigInt(stack[stack.length - 1]) : -1n))
}
stack.push(i)
}
stack = []
for(let i = 0; i <= n; i++) {
while(stack.length > 0 && (i == n || nums[stack[stack.length - 1]] > nums[i])) {
const j = BigInt(stack.pop())
ans -= BigInt(nums[j]) * (BigInt(i) - j) *
(j - (stack.length > 0 ? BigInt(stack[stack.length - 1]) : -1n))
}
stack.push(i)
}
return ans
};