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

【每日一题】子数组的最小值之和

文章目录

  • Tag
  • 题目来源
  • 题目解读
  • 解题思路
    • 方法一:贡献法+单调栈
  • 写在最后

Tag

【贡献法】【单调栈】【数组】【2023-11-27】


题目来源

907. 子数组的最小值之和


题目解读

计算整数数组的连续子数组中最小值的和。


解题思路

本题朴素的解决思想是求出所有的连续子数组,遍历每一个子数组然后将每一个子数组的最小和加和得到结果,但是该方法时间复杂度过高。现在介绍一种时间复杂度较优的方法——贡献法。

方法一:贡献法+单调栈

我们从数组中的每一个元素作为连续子数组中的最小值这一角度考虑,比如在数组 arr = [3, 1, 2, 4] 中,考虑 1 作为子数组中的最小值的情况,有这些子数组满足 1 是子数组中的最小值 :
[ 3 , 1 ] , [ 3 , 1 , 2 ] , [ 3 , 1 , 2 , 4 ] , [ 1 ] , [ 1 , 2 ] , [ 1 , 2 , 4 ] [3, 1], [3, 1, 2], [3, 1, 2, 4], [1], [1, 2], [1, 2, 4] [3,1],[3,1,2],[3,1,2,4],[1],[1,2],[1,2,4]
共计6个。利用乘法原理有:
6 = ( i − a ) ∗ ( b − i ) = 2 ∗ 3 6 = (i - a) * (b - i) = 2 * 3 6=(ia)(bi)=23
其中,a 表示在 arr 数组中 1 左侧比 1 小的第一个元素的下标,默认值为 -1,此时 a = -1
b 表示在 arr 数组中 1 右侧比 1 小的第一个元素的下标,默认值为 n,此时 b = n

以上是 arr 数组中无重复元素的情况,我们只需要找出元素 arr[i] 左侧第一个比它小的元素下标 a、元素 arr[i] 右侧第一个比它小的元素下标 b,利用乘法原理即可得到 arr[i] 作为连续子数组的最小值时的答案,我们遍历数组中的所有元素,计算作为连续子数组的最小值时的答案,将所有答案进行加和得到最终的答案,时间复杂度为 O ( n ) O(n) O(n)

一般的情况,若是 arr 数组中有重复元素出现会怎么样呢?此处参考 贡献法+单调栈+三种实现版本(附题单)Python/Java/C++/Go。

如果 arr 有重复元素,例如 arr=[1, 2, 4, 2, 3, 1],其中第一个 2 和第二个 2 对应的边界都是开区间 (0,5),子数组 [2, 4, 2, 3] 都包含这两个 2,这样在计算答案时就会重复统计同一个子数组,算出错误的结果。

为避免重复统计,可以修改边界的定义,把右边界改为「找小于或等于 arr[i] 的数的下标」,那么:

  • 第一个 2 对应的边界是 (0,3),子数组需要在 (0,3) 范围内且包含下标 1
  • 第二个 2 对应的边界是 (0,5),子数组需要在 (0,5) 范围内且包含下标 3
    这样以第一个 2 为最小值的子数组,就不会「越界」包含第二个 2 了,从而解决了重复统计子数组的问题。

实现代码

#define MOD 1000000007
class Solution {
public:
    int sumSubarrayMins(vector<int>& arr) {
        int n = arr.size();
        vector<int> lLessIdx(n, -1);
        vector<int> rLessAndEquIdx(n, n);

        // 用单调栈更新 lLessIdx
        int i;
        stack<int> stk;
        // [3, 1, 2, 4]
        for(i = 0; i < n; ++i) {
            while(!stk.empty() && arr[stk.top()] >= arr[i]) {
                stk.pop();
            }
            if (!stk.empty()) lLessIdx[i] = stk.top();
            stk.push(i);
        }   

        // 用单调栈更新 rLessAndEquIdx
        while (!stk.empty()) stk.pop();
        for(i = n-1; i >= 0; --i) {
            while(!stk.empty() && arr[stk.top()] > arr[i]) {
                stk.pop();
            }
            if (!stk.empty()) rLessAndEquIdx[i] = stk.top();
            stk.push(i);
        }

        long ans = 0;
        for(i = 0; i < n; ++i) {
            int a = lLessIdx[i], b = rLessAndEquIdx[i];
            ans += (long)((i - a) * (b - i)) % MOD * arr[i] % MOD; // 乘法原理
            ans %= MOD;
        }
        return (int)ans;
    }
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n) n n n 是数组 arr 的长度。

空间复杂度: O ( n ) O(n) O(n)


写在最后

如果文章内容有任何错误或者您对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家有更优的时间、空间复杂度方法,欢迎评论区交流。

最后,感谢您的阅读,如果感到有所收获的话可以给博主点一个 👍 哦。


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

相关文章:

  • minio https配置
  • 使用 ChatGPT 生成和改进你的论文
  • 【C++】构造函数与析构函数
  • 中间件以及主流中间件产品:IBM MQSeries和BEA Tuxedo介绍
  • 【gin】中间件使用之jwt身份认证和Cors跨域,go案例
  • 计算机视觉与深度学习:使用深度学习训练基于视觉的车辆检测器(MATLAB源码-Faster R-CNN)
  • 分享:身份证阅读器在ARM Linux系统调用libwlt2bmp.so解码库实现身份证头像解码
  • python爬虫实习找工作练习测试(以下内容仅供参考学习)
  • 【Linux】make/Makefile 进度条小程序
  • C#,《小白学程序》第二十二课:大数的乘法(BigInteger Multiply)
  • CAM-Classification activation map 类激活图玩耍指南
  • mysql文本类型的最大长度限制
  • 使用VC++设计程序对一幅256级灰度图像进行全局固定阈值分割、自适应阈值分割
  • 单片机毕设实物买的成品,论文是自己查资料和照着实物写的
  • GPS北斗对时服务(时间同步系统)电力变电站应用方案
  • PostgreSQL数据库初接触
  • 使用 OpenCV 发现圆角矩形的轮廓
  • springboot核心原理之@SpringbootApplication
  • CRC校验
  • QT(19):QChar和QByteArray
  • python循环语句和函数
  • 【虹科干货】ntopng如何将漏洞扫描与流量监控相结合,以提高网络安全性
  • OpenCV简介及安装
  • 利用 LD_PRELOAD 环境变量
  • c++版本opencv计算灰度图像的轮廓点
  • 计网Lesson1笔记