2104. 子数组范围和

链接

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
  • -10e9 <= nums[i] <= 10e9

进阶

设计一种时间复杂度为 O(n) 的解决方案

思路一(暴力)

我们可以枚举每个子数组的左下标和右下标,依次统计每个子数组的最大值和最小值的差,将其累加即可。

C++ Code

class Solution {
public:
    long long subArrayRanges(vector<int>& nums) {
        //暴力:两层for循环 枚举左右边界[i,j]
        long long sum=0;
        for(int i=0;i<nums.size();i++)
        {
            int Max=nums[i], Min=nums[i];
            for(int j=i;j<nums.size();j++)
            {            
                Max=max(Max,nums[j]);
                Min=min(Min,nums[j]);
                sum+=Max-Min;
            }
        }
        return sum;
        
    }
};

思路二(单调栈)

题目要求子数组的范围和(所有子数组的最大值和最小值的差值的和),我们可以转换成求所有子数组的最大值的和减去所有子数组的最小值的和。

如果n个子数组的最大值都为A[i],那么A[i]这个元素对sum的贡献就为n*A[i],同理,如果m个子数组的最大值为A[j],那么A[j]这个元素对sum的贡献就是 -m*A[j]。

如何求这个n呢,我们以n个子数组的最大值为A[i]为例,求A[i]作为最大值对sum做贡献时,这个n的求法:找到A[i]左边第一个比它大的数A[left]以及右边第一个比它大的数A[right],也就是求出以A[i]为最大值的最长连续子数组,这里面包含A[i]在内的所有子数组个数为(i-left)*(right-i),总贡献则为(i-left)*(right-i)*A[i],我们举个例子,对于[0,4,2,1,3,2,1,4,0]中下标i=4的元素3,left就是下标为1的4,right就是下标为7的4,这其中包含A[i]的子数组个数就是(4-1)*(7-4)=9,而A[i]作为最大值对sum的贡献就是A[i]*9=3*9=27,同理,我们求出每个元素作为最大值对sum的贡献,以及每个元素作为最小值对sum的贡献,即可求出答案。

至于如何向左或者向右找出第一个大于或者小于A[i]的元素呢?这就需要用到单调栈。我们使用单调递增栈分别预处理出数组minLeft 、minRight以及maxLeft 、maxRight,其中 minLeft[i] 表示 nums[i] 左侧最近的比它小的数的下标,minRight[i] 表示nums[i] 右侧最近的比它小的数的下标,其他同理,这样我们最终的sum就可以直接计算:

sum=

\sum_{0}^{n-1}(i-maxLeft[i])*(maxRight[i]-i)*A[i] -

\sum_{0}^{n-1}(i-minLeft[i])*(minRight[i]-i)*A[i]

那么问题又来了,怎么通过单调栈预处理出数组minLeft 、 minRight以及maxLeft 、maxRight呢?以求解minLeft 为例,从左到右遍历整个数组nums。处理到nums[i] 时,执行出栈操作直到栈为空或者 nums 中以栈顶元素为下标的数逻辑上小于nums[i]。如果栈为空,则minLeft[i]=−1,否则 minLeft[i] 等于栈顶元素,然后将下标 i 入栈。

这里的逻辑上小于定义为:如果nums[i]=nums[j],那么 nums[i] 与nums[j] 的逻辑大小由下标 i 与下标 j的逻辑大小决定,即如果 i < j,那么nums[i] 逻辑上小于nums[j]。

C++ Code

class Solution {
public:
    long long subArrayRanges(vector<int>& nums) {
        int n = nums.size();
        vector<int> minLeft(n), minRight(n), maxLeft(n), maxRight(n);
        stack<int> minStack, maxStack;
        for (int i = 0; i < n; i++) {
            while (!minStack.empty() && nums[minStack.top()] > nums[i]) {
                minStack.pop();
            }
            minLeft[i] = minStack.empty() ? -1 : minStack.top();
            minStack.push(i);
            
            // 如果 nums[maxStack.top()] == nums[i], 那么根据定义,
            // nums[maxStack.top()] 逻辑上小于 nums[i],因为 maxStack.top() < i
            while (!maxStack.empty() && nums[maxStack.top()] <= nums[i]) { 
                maxStack.pop();
            }
            maxLeft[i] = maxStack.empty() ? -1 : maxStack.top();
            maxStack.push(i);
        }
        minStack = stack<int>();
        maxStack = stack<int>();
        for (int i = n - 1; i >= 0; i--) {
            // 如果 nums[minStack.top()] == nums[i], 那么根据定义,
            // nums[minStack.top()] 逻辑上大于 nums[i],因为 minStack.top() > i
            while (!minStack.empty() && nums[minStack.top()] >= nums[i]) { 
                minStack.pop();
            }
            minRight[i] = minStack.empty() ? n : minStack.top();
            minStack.push(i);

            while (!maxStack.empty() && nums[maxStack.top()] < nums[i]) {
                maxStack.pop();
            }
            maxRight[i] = maxStack.empty() ? n : maxStack.top();
            maxStack.push(i);
        }

        long long sumMax = 0, sumMin = 0;
        for (int i = 0; i < n; i++) {
            sumMax += static_cast<long long>(maxRight[i] - i) * (i - maxLeft[i]) * nums[i];
            sumMin += static_cast<long long>(minRight[i] - i) * (i - minLeft[i]) * nums[i];
        }
        return sumMax - sumMin;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值