|
| 1 | + |
| 2 | +>仰望星空的人,不应该被嘲笑 |
| 3 | +
|
| 4 | +## 题目描述 |
| 5 | + |
| 6 | +给定一个整数数组 A,找到 `min(B)` 的总和,其中 B 的范围为 A 的每个(连续)子数组。 |
| 7 | + |
| 8 | +由于答案可能很大,因此返回答案模 `10^9 + 7`。 |
| 9 | + |
| 10 | + |
| 11 | + |
| 12 | +示例: |
| 13 | + |
| 14 | +```javascript |
| 15 | +输入:[3,1,2,4] |
| 16 | +输出:17 |
| 17 | +解释: |
| 18 | +子数组为 [3],[1],[2],[4],[3,1],[1,2],[2,4],[3,1,2],[1,2,4],[3,1,2,4]。 |
| 19 | +最小值为 3,1,2,4,1,1,2,1,1,1,和为 17。 |
| 20 | +``` |
| 21 | + |
| 22 | + |
| 23 | + |
| 24 | +提示: |
| 25 | + |
| 26 | +```javascript |
| 27 | +1 <= A <= 30000 |
| 28 | +1 <= A[i] <= 30000 |
| 29 | +``` |
| 30 | + |
| 31 | +来源:力扣(LeetCode) |
| 32 | +链接:https://leetcode-cn.com/problems/sum-of-subarray-minimums |
| 33 | +著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 |
| 34 | + |
| 35 | +## 解题思路 |
| 36 | +搬运 `jack-108`大佬的<a href="https://leetcode-cn.com/problems/sum-of-subarray-minimums/solution/guan-fang-er-jie-fa-javascript-xiang-jie-by-jack-1/">题解</a>: |
| 37 | + |
| 38 | +既然是求子数组中最小值的和,就是求 以 A[i] 作为最小数能构成的数组有多少个。 |
| 39 | + |
| 40 | +比如 `[2,4,1,2]` ,以`1` 为最小数。能构成的数组数为 `(2+1)*(1+1)` ,`2` 表示 `1 `左面有两个比它大的数,`1` 表示 `1` 右面有一个比它大的数。 |
| 41 | + |
| 42 | +用单调栈求出 `A[i]` 对应的左右最近比 `A[i]` 小的数,记索引为 `prev,next,A[i] `为最小数能形成的数组为 |
| 43 | + |
| 44 | +```javascript |
| 45 | +(i-prev[i])*(next[i]-i) |
| 46 | +``` |
| 47 | + |
| 48 | +这里为什么没有加 `1` 了呢,因为 `prev[i]`已经是比 `A[i]` 小的数了,能和 `A[i]` 形成数组的都是比 `A[i]` 大的数。 |
| 49 | + |
| 50 | +**我的解题方式:** |
| 51 | + |
| 52 | +注释已经足够详细,还是补充一下,我参考了大佬的解题代码,只不过我是直接求出来了以当前 `A[i]` 为最小值的子数组左右两边 大于或等于当前值的个数。这样后续求和直接相乘即可。(不过这里要**强调一下**,如果左边设置大于等于了,右边就只能是大于了,不然会重复计算相等的值) |
| 53 | + |
| 54 | +开始有点看不懂大佬为啥左边初始化为 `-1`,右边初始化为 `A.length` 。假如我们遇到了这种情况,左边都比当前 `A[i]` 要大,那我们维护的单调递减栈就会不断出栈,不断出栈,直到栈为空为止,此时左边个数应该为 `i+1`(从 0 开始计数嘛),那么这部分大佬设为 `-1` 就很巧妙了,问题也就自然明白啦,个人感觉自己写的还是比较好理解一点,不然直接弄一个 `-1` ,第一次用单调栈,还是不太熟... |
| 55 | + |
| 56 | +那么对于右边初始化为 `A.length` ,也是同理啦,当右边都比当前 `A[i]` 要大,那我们维护的单调递减栈就会不断出栈,不断出栈,直到栈为空为止,此时右边个数应该为 `A.length-i`(不用`+1`的原因是从0计数),那么这部分大佬设为 `A.length` 就很巧妙了,依旧清晰明了。 |
| 57 | + |
| 58 | + |
| 59 | +```javascript |
| 60 | +/** |
| 61 | + * @param {number[]} A |
| 62 | + * @return {number} |
| 63 | + */ |
| 64 | +var sumSubarrayMins = function(A) { |
| 65 | + let mod = 1e9+7 |
| 66 | + // 维护一个栈 |
| 67 | + let stack = [] |
| 68 | + // 求以A[i]为最小值的子数组左边大于或等于自己的个数 |
| 69 | + let prev = [] |
| 70 | + for(let i=0;i<A.length;i++){ |
| 71 | + while(stack.length && A[stack[stack.length-1]] >= A[i]) stack.pop() |
| 72 | + // 如果栈为空,即左边都比自己大,则返回i+1,否则返回i-栈顶元素(即保存的下标值) |
| 73 | + prev[i] = stack.length ? i - stack[stack.length-1] : i+1 |
| 74 | + stack.push(i) |
| 75 | + } |
| 76 | + stack = [] |
| 77 | + // 求以A[i]为最小值的子数组右边大于自己的个数(没有等号是因为不会重复计算相等的值) |
| 78 | + let nextv = [] |
| 79 | + for(let i=A.length-1;i>=0;i--){ |
| 80 | + while(stack.length && A[stack[stack.length-1]] > A[i]) stack.pop() |
| 81 | + // 如果栈为空,即右边都比自己大,则返回A.length-i,否则返回栈顶元素(即保存的下标值)-i |
| 82 | + nextv[i] = stack.length? stack[stack.length-1] - i : A.length-i |
| 83 | + stack.push(i) |
| 84 | + } |
| 85 | + let sum = 0 |
| 86 | + for(let i=0;i<A.length;i++){ |
| 87 | + // 以A[i] 为最小值的子数组的组合共有prev[i]*nextv[i]种情况,那么和的话乘以A[i]累加即可 |
| 88 | + sum += (prev[i]*nextv[i]*A[i]) |
| 89 | + // 按题意,进行取模运算 |
| 90 | + sum %= mod |
| 91 | + } |
| 92 | + return sum |
| 93 | +}; |
| 94 | +``` |
| 95 | + |
| 96 | + |
| 97 | +## 最后 |
| 98 | +文章产出不易,还望各位小伙伴们支持一波! |
| 99 | + |
| 100 | +往期精选: |
| 101 | + |
| 102 | +<a href="https://github.com/Chocolate1999/Front-end-learning-to-organize-notes">小狮子前端の笔记仓库</a> |
| 103 | + |
| 104 | +<a href="https://yangchaoyi.vip/">访问超逸の博客</a>,方便小伙伴阅读玩耍~ |
| 105 | + |
| 106 | + |
| 107 | + |
| 108 | +```javascript |
| 109 | +学如逆水行舟,不进则退 |
| 110 | +``` |
| 111 | + |
0 commit comments