LeetCode每日一题——子数组的最小值之和

149 阅读3分钟

问题:

907. 子数组的最小值之和

给定一个整数数组 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

代码:

import java.util.Stack;

class Solution {
    private static final int  inf = 0x3f3f3f3f;
    private static final int mod = 1000000007;
    //单调栈解法
//    public int sumSubarrayMins(int[] arr) {
//        long ans = 0;
//        int n = arr.length;
//        int lft[] = new int[n];
//        int rgt[] = new int[n];
//        // 维护一个递增的单调栈,存储下标
//        Stack<Integer> monoStack = new Stack<Integer>();
//        for (int i = 0; i < n; i++) {
//            // 找出以arr[i]为右端且为最小值的区间数目
//            while(!monoStack.empty() && arr[i] <= arr[monoStack.peek()]){
//                monoStack.pop();
//            }
//            // 若栈空说明整段区间[arr[0], arr[i]]之中arr[i]为最小值,其包含的以arr[i]为右端且为最小值的子区间数目为j + 1
//            // 否则整段区间[arr[j], arr[i]]之中arr[i]为最小值,其以arr[i]为右端且为最小值的子区间数目i - j
//            rgt[i] = i - (monoStack.empty() ? -1 : monoStack.peek());
//            monoStack.push(i);
//        }
//
//        monoStack.clear();
//
//        for (int i = n - 1; i >= 0; i--) {
//            // 找出以arr[i]为左端且为最小值的区间数目
//            while(!monoStack.empty() && arr[i] < arr[monoStack.peek()]){
//                monoStack.pop();
//            }
//            // 若栈空说明整段区间[arr[i], arr[n-1]]之中arr[i]为最小值,其包含的以arr[i]为左端且为最小值的子区间数目为n - i
//            // 否则整段区间[arr[i], arr[j]]之中arr[i]为最小值,其以arr[i]为左端且为最小值的子区间数目j - i
//            lft[i] =  (monoStack.empty() ?  n - i: monoStack.peek() - i);
//            monoStack.push(i);
//        }
//
//
//
//
//        for (int i = 0; i < n; i++) {
//            ans = (ans + (long) lft[i] * rgt[i] * arr[i]) % mod;
//        }
//
//        return (int)ans;
//    }

    // dp+单调栈
    public int sumSubarrayMins(int[] arr){
         /*
         假设s[j][i]为区间j~i的最小值
         则答案应该为所有区间的s[j][i]的和
         暴力解会导致空间超限

         使用dp计算转移方程

         已知以arr[i]为右端的区间为s[0][i]
         设以arr[i]为右端最小值的区间长度为k,则区间[i - k + 1][i]的最小值为arr[i]
         即s[i - k + 1][i] = arr[i]
         剩余区间[0~i - k][i]最小值则必定小于arr[i]且不唯一
         应该为s[0][i - k](表示arr[0]~arr[i - k]的最小值)、s[1][i - k]...

         所求的arr[i]为右端的所有区间最小值和应该为 k * arr[i] + s[0][i - k] + s[1][i - k]...

         所求答案就是每个arr[i]为右端的所有区间最小值和的和

         使用单调栈求k,即求i减去第一个小于arr[i]的值的下标

         设dp[i]表示为s[0][i] ~ s[i][i]的和即 (i∈[0, n))k * arr[i] + s[0][i - k] + s[1][i - k]...
         则dp[i] = k * arr[i] + dp[i - k]
         * */

        long ans = 0;
        int n = arr.length;
        int[] dp = new int[n];
        Stack<Integer> monoStack = new Stack<Integer>();
        for (int i = 0; i < n; i++) {
            while(!monoStack.empty() && arr[i] < arr[monoStack.peek()]){
                monoStack.pop();
            }

            int k = i - (monoStack.empty() ? -1 : monoStack.peek());
            // k有可能为i但是dp[0]是为arr[i]的,所以需要修改一下
            dp[i] = k * arr[i] + (monoStack.empty() ? 0 : dp[i - k]);
            ans  = (ans + dp[i]) % mod;
            monoStack.push(i);
        }

        return (int) ans;
    }
}