【LeetCode每日一题】2104:子数组范围和

216 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第19天,点击查看活动详情

LeetCode每日一题打卡专栏正式启动!不出意外将日更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<=10001 <= nums.length <= 1000
  • 109<=nums[i]<=109-10^9 <= nums[i] <= 10^9

题解一:模拟

遍历所有子数组,保存对应的最小值和最大值,依次求子数组最大值和最小值之差的和即可。

C++代码:

class Solution {
public:
    typedef long long ll;
    int inf=0x3f3f3f3f;
    long long subArrayRanges(vector<int>& nums) {
        int n=nums.size();
        int minnum=inf,maxnum=-inf;
        ll ans=0;
        for(int i=0;i<n;i++){
            minnum=inf,maxnum=-inf;
            for(int j=i;j<n;j++){
                minnum=min(minnum,nums[j]);maxnum=max(maxnum,nums[j]);
                // cout<<i<<"--"<<j<<"--"<<minnum<<"---"<<maxnum<<endl;
                ans+=maxnum-minnum;
            }
        }
        return ans;
    }
};

题解二:单调栈

我们定义:i<j,如果nums[i]==nums[j],则逻辑上我们认为nums[i]<nums[j](因为i<j)。进行此定义为了方便处理单调栈。

题目所求为:所有子数组范围(子数组最大值-最小值)和。可以转化为所有子数组的最大值的和-所有子数组最小值和

假设nums[j]左边第一个比它小的元素为nums[i],nums[j]右边第一个比它小的元素为nums[k]。则所有子数组中以nums[i]为最小值的个数为​。如何获得nums[j]左边和右边第一个比它小的元素的下标呢, 单调栈刚好可以处理,预处理数组minL,minR。其中minL[i]表示nums[i]左边第一个比它小的元素的下标,minR[i]表示nums[i]右边第一个比它小的元素的下标。

单调栈的一个例子:

// 对于数组 [..., 3, 5,6,7,4,1,2]
// 
// 要计算数字5的「右侧比5小的第一个数」的时候
// 需要关注的只有 [6,4,1] 这三个数,也就是单调栈。
// 由于6比5大,所以将6出栈,变成 [4,1],于是找到了,4就是「比5小的右侧第一个数」
// 然后将5入栈,变成 [5,4,1]
//
// 然后继续计算5左边的3的「右侧第一个更小的数」,此时需要考虑的栈是[5,4,1]
// 依次将5, 4出栈,栈变成了[1],终于比3小了,1就是比3小的右侧第一个数。然后将3入栈,变成[3, 1],再继续往左。
// 即,计算「右侧比nums[i]小的第一个数」的时候,要从右往左算。

于是我们以minL处理为例,从左到右遍历数组,当遍历到nums[i]时:

  • 执行出栈,直到栈为空 或者 nums[minstack.top()]逻辑上小于nums[i],
  • 如果栈为空,则minL[i]=-1,否则minL[i]=minstack.top()
  • 然后将下标i入栈

于是所有子数组的最小值和为​,同理求得maxsum。具体见代码:

C++代码:

class Solution {
public:
    typedef long long ll;
    long long subArrayRanges(vector<int>& nums) {
        int n=nums.size();
        stack<int> minstack,maxstack;
        //minL[i]:表示nums[i]左边比它小的第一个元素的下标。其他同理
        vector<int> minL(n),minR(n),maxL(n),maxR(n);
​
        //处理minL[],maxL[]
        for(int i=0;i<n;i++){
            //如果当前栈顶元素大于nums[i],则出栈
            while(!minstack.empty()&&nums[minstack.top()]>nums[i]) minstack.pop();
            minL[i]=minstack.empty()?-1:minstack.top();
            minstack.push(i);
​
            //如果当前栈顶元素小于等于nums[i],则出栈
            //上述已定义了排序规则,若nums[maxstack.top()]==nums[i],则nums[maxstack.top()]<nums[i](因为maxstack.top()<i)
            while(!maxstack.empty()&&nums[maxstack.top()]<=nums[i]) maxstack.pop();
            maxL[i]=maxstack.empty()?-1:maxstack.top();
            maxstack.push(i);
        }
​
        while(!minstack.empty()) minstack.pop();
        while(!maxstack.empty()) maxstack.pop();
        //处理minR[],maxR[]
        for(int i=n-1;i>=0;i--){
            //如果当前栈顶元素大于等于nums[i],则出栈
            //若nums[maxstack.top()]==nums[i],则nums[maxstack.top()]>nums[i](因为maxstack.top()>i)
            while(!minstack.empty()&&nums[minstack.top()]>=nums[i]) minstack.pop();
            minR[i]=minstack.empty()?n:minstack.top();
            minstack.push(i);
​
            //如果当前栈顶元素小于nums[i],则出栈
            while(!maxstack.empty()&&nums[maxstack.top()]<nums[i]) maxstack.pop();
            maxR[i]=maxstack.empty()?n:maxstack.top();
            maxstack.push(i);
        }
​
        // for(int i=0;i<n;i++){
        //     cout<<minL[i]<<"---"<<minR[i]<<"---"<<maxL[i]<<"---"<<maxR[i]<<endl;
        // }
​
        ll maxsum=0,minsum=0;
        for(int i=0;i<n;i++){
            //分别以nums[i]为子数组的最大值或最小值的和,具体见题解描述
            maxsum+=(ll)(maxR[i]-i)*(i-maxL[i])*nums[i];
            minsum+=(ll)(minR[i]-i)*(i-minL[i])*nums[i];
        }
        return maxsum-minsum;
    }
};

Java代码:

class Solution {
    public long subArrayRanges(int[] nums) {
        int n = nums.length;
        int minL[] = new int[n];
        int minR[] = new int[n];
        int maxL[] = new int[n];
        int maxR[] = new int[n];
​
        // Java一般中Deque代替Stack和Queue
        Deque<Integer> minstack = new ArrayDeque<>();
        Deque<Integer> maxstack = new ArrayDeque<>();
        for (int i = 0; i < n; i++) {
            while (!minstack.isEmpty() && nums[minstack.peek()] > nums[i])
                minstack.pop();
            minL[i] = minstack.isEmpty() ? -1 : minstack.peek();
            minstack.push(i);
​
            while (!maxstack.isEmpty() && nums[maxstack.peek()] <= nums[i])
                maxstack.pop();
            maxL[i] = maxstack.isEmpty() ? -1 : maxstack.peek();
            maxstack.push(i);
        }
​
        minstack.clear();
        maxstack.clear();
        for (int i = n - 1; i >= 0; i--) {
            while (!minstack.isEmpty() && nums[minstack.peek()] >= nums[i])
                minstack.pop();
            minR[i] = minstack.isEmpty() ? n : minstack.peek();
            minstack.push(i);
​
            while (!maxstack.isEmpty() && nums[maxstack.peek()] < nums[i])
                maxstack.pop();
            maxR[i] = maxstack.isEmpty() ? n : maxstack.peek();
            maxstack.push(i);
        }
​
        long minsum = 0, maxsum = 0;
        for (int i = 0; i < n; i++) {
            minsum += (long)(i - minL[i]) * (minR[i] - i) * nums[i];
            maxsum += (long)(i - maxL[i]) * (maxR[i] - i) * nums[i];
        }
​
        return maxsum - minsum;
    }
}