LeetCode2104.数组范围和(C++)

给你一个整数数组 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
题目来源:LeetCode2104.数组范围和

思路

单调栈
常规思路是枚举所有区间内的最大值和最小值后进行计算
也可以反过来想,枚举所有元素分别作为最大值和最小值出现的区间的个数
接下来只需求出离每个元素左边和右边最近的最大值和最小值
是单调栈的常规应用


设数 n u m s [ i ] nums[i] nums[i] 是下标为 i i i 的数
其左边最大的数的下标为 l [ i ] l[i] l[i],右边最大的数的下表为 r [ i ] r[i] r[i]
n u m s [ i ] nums[i] nums[i] 是区间 [ l [ i ] + 1 , r [ i ] − 1 ] [l[i] + 1, r[i] - 1] [l[i]+1,r[i]1] 内的最大值
使用计数原理,包含 n u m s [ i ] nums[i] nums[i] 的连续子区间的个数为 ( i − l [ i ] ) ∗ ( r [ i ] − i ) (i - l[i])*(r[i] - i) (il[i])(r[i]i)
最小值同理

另外需要注意的是,数组的可能存在相同的元素
故求两端最近的最值时,其中一端的条件应该弱一些
如左边求严格大于的值,右边求大于等于的值
举例 [ 6 , 4 , 4 , 2 , 8 ] [6,4,4,2,8] [6,4,4,2,8]
对元素 4 4 4 来说:

  • 若条件是两端都求严格大于 4 4 4 的数,则两个元素 4 4 4 都是区间 [ 1 , 3 ] [1,3] [1,3] 之间的最大值,会重复计算
  • 若使右端条件变弱,第一个 4 4 4 是区间 [ 1 , 1 ] [1,1] [1,1] 内的最大值,第二个 4 4 4 是区间 [ 2 , 3 ] [2,3] [2,3] 内的最大值,不会重复计算

代码

class Solution {
public:
    long long subArrayRanges(vector<int>& nums) {
        int n = nums.size();
        long long ans = 0;
        vector<int> l1(n), r1(n), l2(n), r2(n); //分别表示左最大、右大于等于、左最小、右小于等于的数的下标
        stack<int> sl1, sr1, sl2, sr2;

        for (int i = 0; i < n; i ++ ) {
            while (sl1.size() && nums[i] >= nums[sl1.top()]) {
                sl1.pop();
            }
            if (sl1.empty()) l1[i] = -1; //不存在时为-1
            else l1[i] = sl1.top();
            sl1.push(i);
            while (sl2.size() && nums[i] <= nums[sl2.top()]) {
                sl2.pop();
            }
            if (sl2.empty()) l2[i] = -1;
            else l2[i] = sl2.top();
            sl2.push(i);
        }
        for (int i = n - 1; i >= 0; i -- ) {
            while (sr1.size() && nums[i] > nums[sr1.top()]) {
                sr1.pop();
            }
            if (sr1.empty()) r1[i] = n; //不存在时为n
            else r1[i] = sr1.top();
            sr1.push(i);
            while (sr2.size() && nums[i] < nums[sr2.top()]) {
                sr2.pop();
            }
            if (sr2.empty()) r2[i] = n;
            else r2[i] = sr2.top();
            sr2.push(i);
        }
        for (int i = 0; i < n; i ++ ) {
            ans += (long long)nums[i] * (i - l1[i]) * (r1[i] - i); //加上最大值
            ans -= (long long)nums[i] * (i - l2[i]) * (r2[i] - i); //减去最小值
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值