LeetCode困难的中等题——子数组的最小值之和

63 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第30天,点击查看活动详情


最近一直在力扣刷题,也逐渐对各类题型有了自己的理解,所谓见招拆招,将自己的浅显经验分享一下,帮助更多在编程路上的朋友们。


子数组的最小值之和

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

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

 

示例 1:

输入: 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。

示例 2:

输入: arr = [11,81,94,43,3]
输出: 444

 

提示:

  • 1 <= arr.length <= 3 * 104
  • 1 <= arr[i] <= 3 * 104

思路

首先,这道题的核心思想是对于每一个元素,都会有其自身作为最小值的区间,我们只需要找到每一个元素作为最小值的区间数,再与该元素的值相乘,即可得到最终结果。

可以将目标区间分为左右两部分,左半部分的子数组个数为 i - left + 1,右半部分的子数组个数为 right - i + 1,二者乘积即为区间总个数,

例如 arr = [3,1,2,4],对于元素3,其作为最小值的区间只有[3],对于元素1来说,i - left + 1 = 2,right - i + 1 = 3,其作为最小值的区间数为6.

这样的话只需要算出每个元素的区间数,再乘该元素的值,将每种情况累加得到答案。

对于每个元素i,我们需要向左找到比arr[i]小的数arr[left]和向右找到比arr[i]大的数arr[right],对于left和right的求解,需要使用单调栈。

题解

class Solution {
    public int sumSubarrayMins(int[] arr) {
        int MOD = (int) 1e9 + 7;
        int n = arr.length;
        long sum = 0;
        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();
            }
            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();
            }
            right[i] = stack.isEmpty()? n: stack.peek();
            stack.push(i);
        }
        for(int i = 0; i < n; i++) {
            sum += (long) arr[i] * (i - left[i]) * (right[i] - i);
            sum %= MOD;
        }
        return (int) sum;
    }
}