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

题目来源: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;
    }

你可能感兴趣的:(单调栈,leetcode)