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

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

思路: 考虑所有满足 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。

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

代码:

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<Integer> 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<Integer> 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;
    }
}

执行结果:
Alt
Alt
总结: 通过每日一题可以锻炼自己的编程能力,一步步的发现问题,解决问题,对能力的提升很有帮助。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值