当前位置: 首页 > article >正文

力扣(LeetCode)907. 子数组的最小值之和(C++)

枚举

请对题目有疑惑的小伙伴看枚举思想,有助于掌握最基本的解题思路。对于本题数据范围,枚举算法会超时

请看题目描述:给定一个整数数组 arr,找到 min(b) 的总和,其中 b 的范围为 arr 的每个(连续)子数组。
根据示例1:arr = [3,1,2,4]
连续子数组有10个: [3],[1],[2],[4],[3,1],[1,2],[2,4],[3,1,2],[1,2,4],[3,1,2,4]
解题关键:找到10个子数组各自的最小值 3,1,2,4,1,1,2,1,1,1,求和即为示例1的答案。

最简单的做法,二重循环:外层遍历 arr,遍历的位置 i,内层遍历,从 i 向右枚举终点 j (以 i 为起点,长度递增的连续子数组),答案就是求和 [i, j] 区间的最小值minnum

class Solution {
private:
    const int mod = 1e9 + 7;
public:
    int sumSubarrayMins(vector<int>& arr) {
        long long ans = 0;
        for (int i = 0; i < arr.size(); i ++) {
            int minnum = INT_MAX;
            for (int j = i; j < arr.size(); j ++) {
                if (arr[j] < minnum) minnum = arr[j];
                ans = (ans + minnum) % mod;
            }
        }
        return ans;
    }
};

时间复杂度 O ( n 2 ) O(n^2) O(n2):二重循环的时间复杂度 O ( n 2 ) O(n^2) O(n2),本题数据范围会TLE。
空间复杂度 O ( 1 ) O(1) O(1):只使用常数级空间。

单调栈

在枚举算法中,先确定了左边界,枚举右边界,维护区间的最小值。因此时间复杂度是 O ( n 2 ) O(n^2) O(n2)。反过来想,是不是可以枚举数组的每个数,确定以枚举数值为最小值的区间左右边界就好?结论是可以的:单调栈思想。

设枚举位置i,arr[i]的左边界就是从i往左遍历,第一次出现小于arr[i]的数组元素,左边界的位置记为left[i]arr[i]的右边界同理,右边界的位置记为right[i]。对于枚举元素,[左边界, 枚举元素]区间的数,都大于枚举元素本身,提示我们单调栈维护左边界和枚举元素,则下次枚举时找左边界时,只需考虑栈内元素。如对原理有疑惑请评论区提问。

得出计算公式,以arr[i]为最小值的连续区间数: ( ( i − 1 ) − l e f t [ i ] + 1 ) × ( r i g h t [ i ] − ( i + 1 ) + 1 ) = ( i − l e f t [ i ] ) × ( r i g h t [ i ] − i ) ((i-1)-left[i]+1)\times(right[i]-(i+1)+1)=(i-left[i])\times(right[i]-i) ((i1)left[i]+1)×(right[i](i+1)+1)=(ileft[i])×(right[i]i)

arr[i]对答案的贡献: ( i − l e f t [ i ] ) × ( r i g h t [ i ] − i ) × a r r [ i ] (i-left[i])\times(right[i]-i)\times arr[i] (ileft[i])×(right[i]i)×arr[i]

同理计算右边界,反向枚举一遍。

注意:

  • 右边界稍有特别:设枚举位置i,往i右遍历,arr[i]右侧第一次出现小于等于arr[i]的数组元素。这是为了避免重复统计。
  • 设左边界-1表示枚举位置i左侧所有元素小于枚举元素arr[i]。
  • 关键变量:单调栈S,数组left和right。

代码如下,有疑惑请评论区提问。

class Solution {
private:
    const int mod = 1e9 + 7;
    stack<int> S;
public:
    int sumSubarrayMins(vector<int>& arr) {
        long long ans = 0;
        vector<int> left(arr.size()), right(arr.size());
        for (int i = 0; i < arr.size(); i ++) {
            while (S.size() && arr[S.top()] >= arr[i]) S.pop();
            if (S.empty()) left[i] = -1;
            else left[i] = S.top();
            S.push(i);
        }
        S = {};
        for (int i = arr.size() - 1; i >= 0; i --) {
            while (S.size() && arr[S.top()] > arr[i]) S.pop();
            if (S.empty()) right[i] = arr.size();
            else right[i] = S.top();
            S.push(i);
        }
        for(int i = 0; i < arr.size(); i ++) {
            ans = (ans + (long long)(i - left[i]) * (right[i] - i) * arr[i]) % mod;
        }
        return ans;
    }
};

时间复杂度 O ( n ) O(n) O(n):维护单调栈和遍历的时间复杂度 O ( n ) O(n) O(n)
空间复杂度 O ( n ) O(n) O(n):单调栈S、数组leftright的空间复杂度 O ( n ) O(n) O(n)

AC

AC

致语
  • 欢迎读者在评论区留言,期待和大家交流做题感想,分享算法经验~

http://www.kler.cn/a/157281.html

相关文章:

  • 【考研数学:高数2】数列极限
  • 一文了解珈和科技在农业遥感领域的服务内容和能力
  • 计算机新手练级攻略——如何搜索问题
  • 【C++】 list 与 string 基础与实现字符串操作
  • 为什么在Ubuntu下使用VScode开发C++程序时需要手动配置链接库
  • IntelliJ IDEA的快捷键
  • WEB渗透—反序列化(十一)
  • HeteroTiC: 基于异构时间通道的鲁棒网络流水印
  • GCN,GraphSAGE 到底在训练什么呢?
  • 卷积神经网络(CNN):乳腺癌识别.ipynb
  • ChatGPT使用路径:从新手到专家的指南
  • RedisTemplate序列化的问题
  • ElasticSearch学习笔记(一)
  • Redis Hash数据类型
  • C#的参数数组
  • 使用ES6 async awai t进行异步处理
  • python - abstractmethod作用 - `staticmethod`和`abc.abstractmethod`:它会混合吗?
  • Git和Git小乌龟安装
  • make -c VS make -f
  • 电脑发生0x80070002错误,0x80070002错误代码怎么解决
  • G口大带宽是什么意思?
  • Appium:进行iOS自动化测试遇到的问题与解决方案
  • Learning Normal Dynamics in Videos with Meta Prototype Network 论文阅读
  • 网络安全小白自学
  • 【qml入门教程系列】:qml property使用介绍
  • 【static】关键字静态成员:在类级别上共享数据和方法的机制