子数组的最小值之和

源自力扣:

给定一个整数数组 arr,找到 min(b) 的总和,其中 b 的范围为 arr 的每个(连续)子数组。

由于答案可能很大,因此 返回答案模 10^9 + 7 。

实例:

输入:arr = [3,1,2,4]
输出:17
解释:
子数组为 [3],[1],[2],[4],[3,1],[1,2],[2,4],[3,1,2],[1,2,4],[3,1,2,4]。 
最小值为 3,1,2,4,1,1,2,1,1,1,和为 17。

代码如下:

int sumSubarrayMins(vector<int>& arr) {
    int size = arr.size();
    long ans = 0;
    long mod = 1000000007;
    int left[size];
    int right[size];
    stack<pair<int,int>> s;

    for(int i=0;i<size;i++){
        if(s.size()==0){
            left[i]=-1;
            s.push({arr[i],i});
        }else{
            if(arr[i]>s.top().first){
                left[i] = s.top().second;
                s.push({arr[i],i});
            }else{
                //注意,下面的小于判断没有等于
                while((s.size()!=0)&&arr[i]<(s.top().first)){
                    s.pop();
                }
                if(s.size()==0){
                    left[i]=-1;
                    s.push({arr[i],i});
                }else{
                    left[i] = s.top().second;
                    s.push({arr[i],i});
                }
            }
        }
    }
    while(!s.empty()) s.pop();
    for(int i=size-1;i>=0;i--){
        if(s.size()==0){
            right[i]=size;
            s.push({arr[i],i});
        }else{
            if(arr[i]>s.top().first){
                right[i] = s.top().second;
                s.push({arr[i],i});
            }else{
                //注意,下面的小于判断有等于
                while(s.size()!=0&&arr[i]<=s.top().first){
                    s.pop();
                }
                if(s.size()==0){
                    right[i]=size;
                    s.push({arr[i],i});
                }else{
                    right[i] = s.top().second;
                    s.push({arr[i],i});
                }
            }
        }
    }
//    for(auto i : left)
//        cout<<i<<" ";
//    cout<<endl;
//    for(auto i : right)
//        cout<<i<<" ";
    for(int i=0;i<size;i++){
        ans = (ans + (long)arr[i]*(i-left[i])*(right[i]-i))%mod;
    }
    return (int)ans;
}

       由于暴力方法会导致超时,所以我们使用贡献值计算的方法。贡献值计算方法即从所选的某一元素出发,往左和往右走,直到遇到比所选元素小的值便停止。根据乘法原理有:左右边界可以唯一确定字数组 字数组数 = 左边界种类数*右边界种类数,这样便能算出以选定元素为最小值可以产生的子数组个数。

      图源自力扣用户超小白的题解用图,计算如下:

  1. 值为3,下标为0,左边没有最小值,设为-1,右边最近最小值为1,得其下标1,则生成子数组为(0-(-1))*(1-0)=1,则贡献值为3*1=3.
  2. 值为1,下标为1,左边没有最小值,设为-1,右边没有比它小的值,设为5(意味着右边所有元素都能为其子数组),则生成子数组为(1-(-1))*(5-1)=8,则贡献值为8*1=8.
  3. 值为2,下标为2,左边有小值,下标为1,右边也有,下标为4,则生成子数组为(2-1)*(4-2)=2,则贡献值为2*2=4.
  4. 值为4,下标为3,左边有小值,下标为2,右边也有,下标为4,则生成子数组为(3-2)*(4-3)=1,则贡献值为1*4=4.
  5. 注意,这里存在着重复元素1,此时令其左边找小于等于的小值,右边找严格小于,或者左边严格小于,右边小于等于,目的是为了避免重复和不产生遗漏。假如两边严格小于,会导致搜索子数组遗漏,假如两边不严格小于,会导致重复。

       最后把所有贡献值加起来即可。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值