leetcode
leetcode 801 ~ 850
子数组的最小值之和

子数组的最小值之和

难度:

标签:

题目描述

代码结果

运行时间: 83 ms, 内存: 19.2 MB


/*
 * 思路:
 * 1. 由于Java Stream对处理这种复杂的子数组最小值求和不太适用,
 *    这里我们仍然使用单调栈的方法来实现,但用stream来简化代码结构。
 */

import java.util.*;
import java.util.stream.IntStream;

public class Solution {
    public int sumSubarrayMins(int[] arr) {
        int MOD = 1_000_000_007;
        int n = arr.length;
        int[] left = new int[n];
        int[] right = new int[n];
        Stack<Integer> stack = new Stack<>();

        // Fill left array using stream
        IntStream.range(0, n).forEach(i -> {
            while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) {
                stack.pop();
            }
            left[i] = stack.isEmpty() ? -1 : stack.peek();
            stack.push(i);
        });

        // Clear the stack for reuse
        stack.clear();

        // Fill right array using stream
        IntStream.iterate(n - 1, i -> i >= 0, i -> i - 1).forEach(i -> {
            while (!stack.isEmpty() && arr[stack.peek()] >= arr[i]) {
                stack.pop();
            }
            right[i] = stack.isEmpty() ? n : stack.peek();
            stack.push(i);
        });

        // Calculate the result using stream
        return (int) IntStream.range(0, n)
                .mapToLong(i -> (long) arr[i] * (i - left[i]) * (right[i] - i) % MOD)
                .reduce(0, (a, b) -> (a + b) % MOD);
    }
}

解释

方法:

本题解使用单调栈的方法来找出所有子数组的最小值之和。首先,将原数组末尾添加一个0,以保证所有元素都能被正确处理。单调栈用于存储数组元素的索引,且栈中的元素对应的数组值将保持单调不增的顺序。对于每个元素,如果它小于或等于栈顶元素,那么栈顶元素及其之前的元素将依次出栈,直到栈顶元素小于当前元素。对于每个出栈的元素t,计算以arr[t]为最小值的子数组的数量,乘以arr[t],再累加到结果中。这样,每个元素在出栈时,可以算出以它为最小值的所有连续子数组对结果的贡献。最终,返回累加的结果,对1,000,000,007取模。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
为什么在数组末尾添加一个0,这样做有什么特殊的作用或优势?
在数组末尾添加一个0的主要作用是确保所有数组中的元素都能被处理并出栈。因为0是一个非常小的值,当遍历到这个0时,它会引起栈中所有当前的元素依次出栈。这样,每个元素都会有机会被计算在内,确保了算法的完整性和正确性。添加0简化了代码逻辑,避免了在循环结束后还需要单独处理栈中剩余元素的复杂情况。
🦆
在单调栈中使用-1作为哨兵元素有什么具体的意义或好处?
在单调栈中使用-1作为哨兵元素主要是为了处理边界情况,使得栈中始终有一个元素。这样在计算以某个元素为最小值的子数组数量时可以方便地使用索引差来计算。如果栈为空,则需要特别处理,哨兵元素-1简化了这些计算过程,避免了对空栈的额外检查,使得算法更加简洁和高效。
🦆
你提到对每个出栈的元素计算以arr[t]为最小值的子数组的数量,具体是如何计算的?
对于每个出栈的元素t,计算以arr[t]为最小值的子数组的数量涉及到两部分:1) t左侧的子数组范围,2) t右侧的子数组范围。具体来说,arr[t]左侧的第一个比arr[t]小的元素的位置是s[-1],因此左侧有(t - s[-1])种可能的起始位置;右侧的第一个比arr[t]小的元素的位置是idx,因此右侧有(idx - t)种可能的结束位置。因此,以arr[t]为最小值的子数组的总数是(t - s[-1]) * (idx - t)。
🦆
该算法中,如果栈顶元素等于当前元素,也会导致栈顶元素出栈,这样处理的原理是什么?
当栈顶元素等于当前元素时,也让栈顶元素出栈的原理是为了保证所有子数组的最小元素都能被正确计算。如果允许栈里有重复值,则可能会造成重复计算某些子数组的贡献或者遗漏某些情况。通过确保栈中元素严格单调递增,可以简化计算过程,并确保每个子数组的最小值只计算一次,这有助于维护算法的正确性和效率。

相关问题