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

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/sum-of-subarray-minimums
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方法 1:前驱 / 后继数组

想法

考虑所有满足 A[j] 为最右且最小的元素的子序列个数 #(j),那么结果就是 sum #(j) * A[j]。(我们必须考虑最右这样才可以构造互不相交的子序列,否则会出现多次计算,因为一个数组的最小值可能不唯一。)

这就变成考虑最小的下标 i <= j 满足 A[i], A[i+1], ..., A[j] 都 >= A[j] 以及最大的下标 k >= j 满足 A[j+1], A[j+2], ..., A[k] 都 > A[j]。

算法

例如,A = [10, 3, 4, 5, _3_, 2, 3, 10] 我们需要计算 #(j = 4),也就是第二个数字 3 ,被标记的那个,我们会发现 i = 1 和 k = 5。

由此,实际的总数为 #(j) = (j - i + 1) * (k - j + 1) 其中 j - i + 1 选择子序列的左端点 i, i+1, ..., j,而 k - j + 1 选择子序列的右端点 j, j+1, ..., k。

对于每个询问(也就是根据 j 计算出 (i, k))是一个经典问题,可以用一个栈来解决。我们会重点解答如何找到 i,而找到 k 的做法与之类似。

构造前序数组

做法是维护一个栈,A 的单调下降子序列(事实上是维护一个 A 的下标索引)。对于下一个询问,候选界限为 i* - 1,其中 A[i*] 以递增顺序存储。

现在考虑升序下标 j ,我们可以按照 i* 递减顺序移走所有 A[i*] <= A[j]。

例如,假设 A = [10, 5, 3, 7, 0, 4, 5, 2, 1, _8_] 那么当考虑 j = 9 (A[j] = 8),我们有一个存储边界的栈类似于 [-1, 0, 3, 6](代表 A[i*] = -inf, 10, 7, 5)。我们从栈中弹出 6 和 3 因为 5 <= 8 且 7 <= 8,因此得到询问的边界为 i* - 1 = 0。

注意这个过程线性的,因为只进行了线性次的入栈和出栈。

这种办法很难想到,但在很多地方都有用到,所以很值得学习其细节。

作者:LeetCode
链接:https://leetcode.cn/problems/sum-of-subarray-minimums/solution/zi-shu-zu-de-zui-xiao-zhi-zhi-he-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

import java.util.ArrayDeque;
import java.util.Deque;

public class Solution {

    public int sumSubarrayMins(int[] A) {
        int MOD = 1_000_000_007;
        int N = A.length;

        // 第 1 步:计算当前下标 i 的左边第 1 个比 A[i] 小的元素的下标
        Deque stack1 = new ArrayDeque<>();
        int[] prev = new int[N];
        for (int i = 0; i < N; i++) {
            while (!stack1.isEmpty() && A[i] <= A[stack1.peekLast()]) {
                stack1.removeLast();
            }
            prev[i] = stack1.isEmpty() ? -1 : stack1.peekLast();
            stack1.addLast(i);
        }

        // 第 2 步:计算当前下标 i 的右边第 1 个比 A[i] 小的元素的下标
        Deque stack2 = new ArrayDeque<>();
        int[] next = new int[N];
        for (int i = N - 1; i >= 0; i--) {
            while (!stack2.isEmpty() && A[i] < A[stack2.peekLast()]) {
                stack2.removeLast();
            }
            next[i] = stack2.isEmpty() ? N : stack2.peekLast();
            stack2.addLast(i);
        }

        // 第 3 步:计算结果
        long ans = 0;
        for (int i = 0; i < N; ++i) {
            // 注意:乘法可能越界,须要先转成 long 类型
            ans += (long) (i - prev[i]) * (next[i] - i) % MOD * A[i] % MOD;
            ans %= MOD;
        }
        return (int) ans;
    }
}


作者:LeetCode
链接:https://leetcode.cn/problems/sum-of-subarray-minimums/solution/zi-shu-zu-de-zui-xiao-zhi-zhi-he-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

你可能感兴趣的:(java,大数据)