力扣 907. 子数组的最小值之和

本文介绍了一种利用单调栈求解LeetCode题目“子数组最小值之和”的高效算法。通过寻找每个元素作为最小值时的左右边界,避免了直接枚举导致的时间复杂度过高问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目来源:https://leetcode.cn/problems/sum-of-subarray-minimums/

大致题意:
给一个数组,求数组所有子数组的最小值的和


思路

如果直接枚举所有子数组,然后统计最小值的和会超时

这一类统计子数组最值的题可以使用单调栈来解决,即使用单调栈求出每个元素作为最值的子数组的边界

如该题,使用单调栈求出第 i 个位置元素为最小值的子数组边界为 (l, r)该区间为开区间,即不包含 l 和 r,那么该边界中子数组的个数即为 (r - i) * (i - l),也就是有 (r - i) * (i - l) 个子数组最小值为第 i 个位置元素,于是第 i 个位置对整体答案的贡献即为 nums[i] * (r - i) * (i - l)

单调栈
  1. 使用单调栈求出数组中每个元素左侧小于它的第一个元素位置 l (如果没有则为 -1),和右侧小于等于它的第一个元素位置 r(如果没有则为数组长度),于是 (l, r) 内即为当前元素作为最小值的所有子数组

左侧小于,右侧小于等于是为了防止出现区间重合的情况
如 [3, 1, 2, 1, 3],如果两侧都是寻找小于当前元素的位置,那么两个 1 找到的边界都会为 (-1, 5),出现重合,会重复统计子数组的个数。如果左侧小于,右侧小于等于,那么第一个 1 统计的边界为 (-1, 3),第二个 1 统计的边界为 (-1, 5)

  1. 根据边界值求出每个元素对答案的贡献,统计结果
public int sumSubarrayMins(int[] arr) {
        int n = arr.length;
        // 所有位置的左边界
        int[] left = new int[n];
        // 所有位置的右边界
        int[] right = new int[n];
        // 单调栈
        Deque<Integer> stack = new ArrayDeque<>();
        for (int i = 0; i < n; i++) {
            // 在单调栈中第一个小于当前元素的值(大于等于不满足,弹出栈)
            while (!stack.isEmpty() && arr[stack.peek()] >= arr[i]) {
                stack.pop();
            }
            // 如果单调栈为空,表示之前出现的元素没有小于当前元素的,左边界值为 -1
            // 否则,左边界值即为对应元素位置
            left[i] = stack.isEmpty() ? -1 : stack.peek();
            stack.push(i);
        }
        stack.clear();
        for (int i = n - 1; i >= 0; i--) {
            // 在单调栈中第一个小于等于当前元素的值(大于不满足,弹出栈)
            while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) {
                stack.pop();
            }
            // 如果单调栈为空,表示之前出现的元素没有小于等于当前元素的,右边界值为 n
            // 否则,右边界值即为对应元素位置
            right[i] = stack.isEmpty() ? n : stack.peek();
            stack.push(i);
        }
        int MOD = 1000000007;
        long ans = 0;
        for (int i = 0; i < n; i++) {
            // 当前数组作为最小值的子数组个数
            int count = (right[i] - i) * (i - left[i]);
            // 统计贡献值
            ans = (ans + (long) count * arr[i] % MOD) % MOD;
        }
        return (int) ans;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三更鬼

谢谢老板!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值