题目描述

给定一个整数数组 A,找到 min(B) 的总和,其中 B 的范围为 A 的每个(连续)子数组。

由于答案可能很大,因此返回答案模 10^9 + 7。

示例1

        输入:
[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。

提示

  • 1 <= A.length <= 30000
  • 1 <= A[i] <= 30000

题解

这题意思是,遍历所有的连续子数组,然后求所有子数组中最小值之和。

暴力法

遍历所有区间,然后对于每个区间找出最小值求和。这种方法时间复杂度是 ,显然不可行。

暴力法优化

对于区间左端点 i ,遍历所有的右端点 j ,然后维护最小值,时间复杂度可以降到 ,但还是太高了。

单调栈

既然我们不能先遍历区间,然后找最小值,那么我们不如顺序倒过来,对于每个值,我们找有多少区间里面,它是最小值。

对于一个数字 A[i] 来说,如果在某个区间 [j, k] 里面它是最小值,那么 [j, k] 包含 A[i] 的子数组的最小值也一定是 A[i] 。所以我们只需要找出最大的那个区间,使得 A[i] 是最小值就行了。

另一个性质是,左右端点 j 和 k 是相互独立的,不会影响,因为 [i, k] 的改变并不会改变 [j, i] 的最小值。所以我们只需要分别求出 A[i] 往左和往右的最远距离就行了。

因为往左和往右求解方法是类似的,所以我们只需要看一个方向就行了。同样不能遍历一遍,不然就和暴力法没区别了嘛。这时候就要介绍神器了——单调栈

单调栈是一个栈,后进先出,里面的元素是单调递增或递减的。而在这题里面,我们要求的是 A[i] 左边最远的距离,等价于求左边第一个比它小的数字 A[j] 。而 A[j+1], ..., A[i] 都大于等于 A[i] ,所以都可以作为符合要求区间的左端点。

这里单调栈只需要维护一个单调上升的子序列就行了,遍历到一个数 A[i] 的时候,如果栈顶的元素大于等于 A[i] ,那么就出栈,直到第一个小于 A[i] 的数 A[j] 为止,那么 A[i] 为最小值的区间左端点可选择数量为 j - i。为什么这样是对的呢?因为 A[j] 是栈里面第一个小于 A[i] 的数,而 A[j] 后面的数都大于 A[j] ,这样才不会把 A[j] 顶出栈。而如果栈是空的,就说明 A[i] 前面的所有元素都大于等于它,那么所有区间都符合条件了。

而右边最大的范围同理可以求得,但是这里有个需要注意的地方!如果存在两个相同的数,这么算不是会导致同一个区间在两个数的位置处计算两次吗?所以要稍稍改进一下,既然向左计算的时候,已经包含了相等的值了,那么向右计算就要排除掉了。也就是从右往左计算右边最远范围的时候,只能计算右边第一个小于等于它的位置,而向左是计算第一个小于它的位置。这样就不会重复计算了。

单调栈+动态规划

上面的方法不仅要考虑两端的范围,还得考虑去重,真是麻烦又容易写错。下面介绍一种更加好写又不容易写错的方法,只是不那么容易想到。

我们定义 sum[i] 为所有以 i 为右端点的区间的最小值之和,同样用单调队列的方法来寻找左边最远的距离,使得区间内 A[i] 是最小值。假设用单调队列找到了左边第一个比 A[i] 小的数是 A[j] ,那么 sum[i] 就可以加上 (i - j) * A[i] ,因为 A[j] 往右都是 A[i] 最小。而 A[j] 再往左呢?这些区间最小值等价于直接以 A[j] 为右端点的最小值,因为 A[j] 往右的数都比它大,没有影响,所以 sum[i] 再加上 sum[j] 就行了。

上面两种方法时间复杂度都是 的,因为进栈出栈最多也只需要 2n 次。

代码

单调栈(c++)

        class Solution {public:static const int MOD = 1e9 + 7;int sumSubarrayMins(vector<int>& A) {int n = A.size();vector<int> pre(n, 0), nxt(n, 0);stack<int> sp, sn;for (int i = 0; i < n; ++i) {while (!sp.empty() && A[sp.top()] >= A[i]) sp.pop();pre[i] = sp.empty() ? i + 1 : i - sp.top();sp.push(i);}for (int i = n-1; i >= 0; --i) {while (!sn.empty() && A[sn.top()] > A[i]) sn.pop();nxt[i] = sn.empty() ? n - i : sn.top() - i;sn.push(i);}int res = 0;for (int i = 0; i < n; ++i) {(res += ((pre[i] * nxt[i]) % MOD * A[i]) % MOD) %= MOD;}return res;}
};

单调栈(python)

        class Solution:def sumSubarrayMins(self, A: List[int]) -> int:MOD = 1e9 + 7n = len(A)pre = [0] * nnxt = [0] * nsp = []sn = []for i in range(n):while len(sp) != 0 and A[sp[-1]] >= A[i]:sp.pop()pre[i] = i + 1 if len(sp) == 0 else i - sp[-1]sp.append(i)for i in range(n-1, -1, -1):while len(sn) != 0 and A[sn[-1]] > A[i]:sn.pop()nxt[i] = n - i if len(sn) == 0 else sn[-1] - isn.append(i)res = sum([pre[i] * nxt[i] * A[i] for i in range(n)]) % MODreturn int(res)

单调栈+动态规划(c++)

        class Solution {public:static const int MOD = 1e9 + 7;int sumSubarrayMins(vector<int>& A) {int n = A.size();vector<int> sum(n, 0);stack<int> sp;for (int i = 0; i < n; ++i) {while (!sp.empty() && A[sp.top()] >= A[i]) sp.pop();sum[i] = sp.empty() ? (i + 1) *  A[i] : (i - sp.top()) * A[i] + sum[sp.top()];sum[i] %= MOD;sp.push(i);}int res = 0;for (int i = 0; i < n; ++i) {(res += sum[i]) %= MOD;}return res;}
};

单调栈+动态规划(python)

        class Solution:def sumSubarrayMins(self, A: List[int]) -> int:MOD = 1e9 + 7n = len(A)summ = [0] * nsp = []for i in range(n):while len(sp) != 0 and A[sp[-1]] >= A[i]:sp.pop()summ[i] = (i + 1) * A[i] if len(sp) == 0 else (i - sp[-1]) * A[i] + summ[sp[-1]]sp.append(i)res = sum(summ) % MODreturn int(res)

后记

这题虽然是个中等难度的题,但是还是比一些难题难做一些的,通过这题主要去学会单调栈的使用。

每日算法系列【LeetCode 907】子数组的最小值之和相关推荐

  1. LeetCode 907. 子数组的最小值之和(单调栈)

    文章目录 1. 题目 2. 解题 1. 题目 给定一个整数数组 A,找到 min(B) 的总和,其中 B 的范围为 A 的每个(连续)子数组. 由于答案可能很大,因此返回答案模 10^9 + 7. 示 ...

  2. leetcode 907. Sum of Subarray Minimums | 907. 子数组的最小值之和(单调栈)

    题目 https://leetcode.com/problems/sum-of-subarray-minimums/ 题解 单调栈问题.参考左神算法课:https://ke.qq.com/webcou ...

  3. 重复次数最多的 子串_每日算法系列【LeetCode 424】替换后的最长重复字符

    题目描述 给你一个仅由大写英文字母组成的字符串,你可以将任意位置上的字符替换成另外的字符,总共可最多替换 k 次.在执行上述操作后,找到包含重复字母的最长子串的长度. 示例1 输入: s = &quo ...

  4. 【每日一题】 643. 子数组最大平均数 I

    [每日一题] 643. 子数组最大平均数 I 避免每日太过咸鱼,一天搞定一道LeetCode算法题 一.题目描述 难度: 简单 给定 n 个整数,找出平均数最大且长度为 k 的连续子数组,并输出该最大 ...

  5. 数组最大可以开多大_每日算法系列【LeetCode 689】三个无重叠子数组的最大和

    题目描述 给定数组 由正整数组成,找到三个互不重叠的子数组的最大和. 每个子数组的长度为 ,我们要使这 个项的和最大化. 返回每个区间起始索引的列表(索引从 0 开始).如果有多个结果,返回字典序最小 ...

  6. leetcode算法题--连续的子数组的和

    原题链接:https://leetcode-cn.com/problems/continuous-subarray-sum/ 1.暴力 这题用暴力方法非常简单的 bool checkSubarrayS ...

  7. LeetCode - 644 子数组最大平均数 II

    目录 题目来源 题目描述 示例 提示 题目解析 算法源码 题目来源 644. 子数组最大平均数 II - 力扣(LeetCode) 题目描述 给定一个包含 n 个整数的数组nums,找到最大平均值的连 ...

  8. LeetCode 2104. 子数组范围和(单调栈)

    文章目录 1. 题目 2. 解题 1. 题目 给你一个整数数组 nums .nums 中,子数组的 范围 是子数组中最大元素和最小元素的差值. 返回 nums 中 所有 子数组范围的 和 . 子数组是 ...

  9. LeetCode 1856. 子数组最小乘积的最大值(前缀和 + 单调栈)

    文章目录 1. 题目 2. 解题 1. 题目 一个数组的 最小乘积 定义为这个数组中 最小值 乘以 数组的 和 . 比方说,数组 [3,2,5] (最小值是 2)的最小乘积为 2 * (3+2+5) ...

  10. LeetCode 898. 子数组按位或操作(前缀和思想)

    文章目录 1. 题目 2. 解题 2.1 超时解 2.2 正解 1. 题目 我们有一个非负整数数组 A. 对于每个(连续的)子数组 B = [A[i], A[i+1], ..., A[j]] ( i ...

最新文章

  1. PHPCMS商城:模块_购物车+订单模块(资源合集)
  2. 干货!639页《深度学习:Deep Learning》硬核课程PPT
  3. 1-3-04:带余除法
  4. 如何动态改变Table Control的列抬头
  5. bigdecimal不保留小数_为什么 0.1 + 0.2 = 0.3,原来你不知道
  6. 真香?小米9价格将上4000元!战斗天使真机长这样...
  7. oracle零碎要点---oracle em的web访问地址忘了
  8. 用c语言编电话薄系统,求助 哈稀表编电话薄程序(c语言) 算法
  9. 利用PCL做点云的平面拟合
  10. 【优化算法】粒子群的混沌混合蝴蝶优化算法【含Matlab源码 047期】
  11. BCS2022|“体系化防御,数字化运营” 奇安信终端安全能力再升级
  12. 比赛报名 | 2019AIIA杯电梯调度算法大赛正式启动
  13. 电压型逆变器和电流型逆变器的不同
  14. 积木开发系列----Blockly初体验
  15. 地球毁灭日.3九星连珠
  16. apktool d --no-res douyin.apk -o out1
  17. GIS(三)——优化js版搜狗地图的brand标牌样式
  18. 轻量级开源网盘系统分享
  19. 济南区县(功能区)分布
  20. 基于Java+Swing+Socket实现泡泡堂游戏

热门文章

  1. ios网络学习------1get post异步请求
  2. 利用 Arrays.sort 字符串 进行排序 完全按字符 排序 忽略字符大小写
  3. Codeforces Beta Round #4 (Div. 2 Only) D. Mysterious Present(LIS)
  4. mysql补0操作有什么意义?
  5. [转] 英文写作中省略号的使用
  6. 笔记5 bean的作用域
  7. c#抽取pdf文档标题(1)
  8. 针对不同手机系统的LBS地图定位解决方案
  9. [转]自定义jsp标记库简介
  10. PaddlePaddle(1)——让人拍案叫绝的创意都是如何诞生的