一、题目
给定一个整数数组 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
二、代码
class Solution {
public int sumSubarrayMins(int[] arr) {
int n = arr.length;
// 创建单调栈
int top = -1;
int[] stack = new int[n];
long sum = 0;
// 执行单调栈流程,找到每一个位置的一个最大范围,这个范围内的最小值就是这个位置上的值
for (int i = 0; i < n; i++) {
// 也就是找到这个位置左右两边最近的并且小于它的数,再找到的这两个数之间的范围中,最小值就是当前这个位置,然后就在这个范围内找到以这个值为最小值的子数组即可
while (top != -1 && arr[stack[top]] >= arr[i]) {
int index = stack[top--];
// 找到左右两个边界
int leftIndex = top == -1 ? -1 : stack[top];
int rightIndex = i;
// (index - leftIndex):这个求的在这个范围内所有以index位置的值为最小值的子数组的全部开始点有多少个
// (rightIndex - index):这个求的在这个范围内所有以index位置的值为最小值的子数组的全部结束点有多少个
// 起始点个数 * 结束点个数就等于所有的子数组个数
// 而这些子数组的都是以arr[index]为最小值,所以所以再乘上arr[index]即可得到最小值累加和
// 注意:再找这个大区域内的以arr[index]为最小值的子数组时,必须要让这个子数组的范围包含index位置才行,毕竟是要以index位置的值为最小值,必须得包括index位置。所以子数组的起始点下标一定小于等于index,子数组的结束点下标一定大于等于index,这样才能保证Index位置的值能包含进去
// 这里还有一个注意点就是要对arr[index]类型转化为Long,这样就能将乘出来的结果自动转换为Long型,Java中自动转化都是向位数多的类型转化,避免数据溢出。sum已经是long值了
sum += (index - leftIndex) * (rightIndex - index) * (long)arr[index];
}
// 加入单调栈
stack[++top] = i;
}
// 处理剩余的单调栈节点
while (top != -1) {
int index = stack[top--];
int leftIndex = top == -1 ? -1 : stack[top];
int rightIndex = n;
sum += (index - leftIndex) * (rightIndex - index) * (long) arr[index];
}
// 这里要对1000000007取余
return (int) (sum % 1000000007);
}
}
三、解题思路
这道题的一个核心思路就是把这个大数组中所有可能为子数组最小值的数列出来,然后分别看以这个数为最小值的子数组有哪些,然后就容易求累加了。数组中每一个值都有可能成为某个子数组的最小值,所以就分别去求以每一个位置为最小值的子数组有哪些就行了。如果老是想着把所有的子数组都找出来,然后再去找每个子数组的最小值,那这辈子也写不出来这道题了,所以一定要学会转换视角,我们可以把最小值固定好,然后去找以某个值为最小值的子数组有哪些,这样就好算了。
版权声明:本文为cy973071263原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。