单调栈的一个简单应用(Leetcode 907)

Leetcode 907. 子数组的最小值之和

基本思路:
找到每个数能“管辖的区域”,计算 ∑ i = 0 l e n − 1 a r r [ i ] × 区 域 个 数 \sum_{i=0}^{len-1} arr[i]\times区域个数 i=0len1arr[i]×。因为有很多子数组的 min 是一样的,所以不用遍历 arr 中的所有子数组,然后求最小值,而是反过来,把所有可能的最小值,也就是把 arr[i] 从 0 到 len - 1 遍历一遍,计算 arr[i] * 对应数组个数 就好。

现在的问题就是,如何计算每个 arr[i] 对应的数组个数?
简单分析一下,arr[i] 对应的数组,其最小值是 arr[i],也就是说其余值都比 arr[i] 大。这非常符合单调栈的思路。

所谓单调栈,就是栈中的元素满足单调递增 / 递减的顺序。至于单调栈如何构建,这里不做说明,请自行查找资料。
单调栈,重点不是在于最终栈中保存了什么内容,而是在于:元素为什么能入栈,为什么能出栈?以递增栈为例,入栈是因为前面那个元素比它小,而出栈是因为后面的那个元素也比它小。这样就找到了某个元素左右两边最近的比它小的两个数。
换言之,对于 arr[i],若在左右两边分别找到了最近的比它小的数,左边为 arr[l - 1],右边为 arr[r + 1],那么我们也就找到了一个区间 arr[l] ~ arr[r],这个区间中,除了 arr[i] 外,其它所有元素都比 arr[i]

在这里插入图片描述


说得稍微形象一些,就是包含这个低点的最大峰谷。也就是所谓的“管辖区域”了。
找到管辖区域 [l, r] 后,根据乘法原理,满足 min 为 arr[i] 子数组个数,为 (i - l + 1) * (r - i + 1)。这样就求到了最小值为 arr[i] 情况下的和。
当然,最小值有很多,从 arr[0], arr[1], arr[2]arr[n - 1],只要在 arr 数组中就会成为最小值。然后把所有的最小值和对应的数组个数相乘并求和就好了。也就是一开始的那个公式: ∑ i = 0 l e n − 1 a r r [ i ] × 区 域 个 数 \sum_{i=0}^{len-1} arr[i]\times区域个数 i=0len1arr[i]×

class Solution {
    int mod = 1000000007;
    public int sumSubarrayMins(int[] arr) {
        Stack<Integer> stack = new Stack<>();
        int res = 0;
        for(int i = 0; i < arr.length; i++) {
            while(!stack.isEmpty() && arr[i] < arr[stack.peek()]) {
                int idx = stack.pop();
                int begin = 0;
                if(!stack.isEmpty()) {
                    begin = stack.peek() + 1;
                }
                res = (int)((res + (long) arr[idx] * (idx - begin + 1) * (i - idx)) % mod);
            }
            stack.push(i);
        }
        // System.out.println(res + "   " + stack.peek());

        while(!stack.isEmpty()) {
            int idx = stack.pop();
            int begin = 0;
            if(!stack.isEmpty()) {
                begin = stack.peek() + 1;
            }
            res = (int)((res + (long) arr[idx] * (idx - begin + 1) * (arr.length - idx)) % mod);
        }
        return res;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值