Leetcode 907.子数组的最小值之和(中等)

news2025/1/11 20:48:00

一、题目

1、题目描述

给定一个整数数组 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

2、基础框架

class Solution {
public:
    int sumSubarrayMins(vector<int>& arr) {

    }
};

3、原题链接

907.子数组的最小值之和

二、解题报告

1、思路分析

整体思路

求的以每个位置的值为最小值能得到的子数组个数。

流程
假设 10 位置是 7, 左边离它最近比它小的是 5 位置的 5,右边离它最近比它小的是 15 位置的 4。那么以 7 作为最小值的子数组范围为 6 ~ 14,但这个范围中并不是所有的子数组都是以 7 作为最小值的,比如假设 6 位置是 10, 7 位置是 8,那么6位置作为子数组最小值是10,7位置作为子数组的最小值是8,所以必须要跨过 10 位置的 7 的子数组才是以 7 作为最小值的。

示例:
在这里插入图片描述
以 6 位置开头的:6 ~ 10、6 ~ 11、6 ~ 12、6 ~ 13、6 ~ 14 这些都是以 7 作为最小值的子数组。

同理还有 7 位置开头的,8 位置开头的,9 位置开头的,10 位置开头的子数组。

那么整体数量是多少呢?

6 ~ 10 共 5 个数,10 ~ 14 共 5 个数,所以总的以 7 为最小值的子数组个数为 5 * 5 = 25 个,产生的子数组最小值累加和为 25 * 7 = 175。

抽象化
i i i 位置的 x x x​,左边离它最近的是比它小的 k k k 位置的 y y y​, 右边离它最近比它小的是 j j j 位置的 z z z​,以 x x x 为最小值的范围为 ( k , j ) (k, j) (k,j),那么产生以 x x x 为最小值的子数组数量为 ( i − k ) ∗ ( j − i ) (i−k) ∗ (j−i) (ik)(ji)​,以 i i i 位置的 x x x 值为最小值产生的子数组的累加和为 ( i − k ) ∗ ( j − i ) ∗ x (i−k)∗(j−i)∗x (ik)(ji)x

有重复值的情况需要改进:

数组:2 4 5 3 6 6 6 3 7 8 6  3  5  3  2
下标:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
		                   
那么以 3 位置的 3 作为最小值,能扩充的范围为 1 ~ 6, 产生的子数组为:
       1 ~ 3,1 ~ 4, 1 ~ 5,1 ~ 6
    	 2 ~ 3,2 ~ 4, 2 ~ 5, 2 ~ 6
    		3 ~ 3, 3 ~ 4, 3 ~ 5, 3 ~ 6
而 7 位置的 3 作为最小值,能扩充的范围为 1 ~ 10,所以产生的子数组为:
     1 ~ 7, 1 ~ 8, 1~ 9, 1 ~ 10
    	2 ~ 7, 2 ~ 8, 2 ~ 9, 2 ~ 10
    		......
    		7 ~ 7, 7 ~ 8, 7 ~ 9,  7 ~ 10
    注意,这个3是包含了前面的3的范围的

就是结尾处在相等位置的时候停住,目的是为了去重。

2、时间复杂度

O ( n ) O(n) O(n)

3、代码详解

C++版

class Solution {
public:
    int sumSubarrayMins(vector<int>& arr) {
        if (arr.size() == 0) return 0;

        long ans = 0;
        int left[arr.size()];
        int right[arr.size()];
        memset(left, 0, sizeof(left));
        memset(right, 0, sizeof(right));
        nearLessLeftAndLessEqualRight(arr, left, right);
        
        for (int i = 0; i < arr.size(); i++) {
            int start = i - left[i]; //子数组起点个数
            int end = right[i] - i; //子数组终点个数
            ans += start * end * (long) arr[i];
            ans %= 1000000007;
        }

        return ans;
    }

	// 左边 < arr[i], 右边 >= arr[i] 的位置构成的范围
    void nearLessLeftAndLessEqualRight(vector<int> &arr, int *left, int *right) {
        int n = arr.size();
        int _stack[n];
        int si = -1;

        for (int i = 0; i < n; i++) {
            while (si != -1 && arr[_stack[si]] >= arr[i]) {
                int cur = _stack[si--];
                left[cur] = si == -1 ? -1 : _stack[si];
                right[cur] = i;
            }
            _stack[++si] = i;
        }

        while (si != -1) {
            int cur = _stack[si--];
            left[cur] = si == -1 ? -1 : _stack[si];
            right[cur] = n;
        }
    }
};

Java版

// 测试链接:https://leetcode.com/problems/sum-of-subarray-minimums/
// subArrayMinSum1是暴力解
// subArrayMinSum2是最优解的思路
// sumSubarrayMins是最优解思路下的单调栈优化
// Leetcode上不要提交subArrayMinSum1、subArrayMinSum2方法,因为没有考虑取摸
// Leetcode上只提交sumSubarrayMins方法,时间复杂度O(N),可以直接通过
public class SumOfSubarrayMinimums {

	public static int subArrayMinSum1(int[] arr) {
		int ans = 0;
		for (int i = 0; i < arr.length; i++) {
			for (int j = i; j < arr.length; j++) {
				int min = arr[i];
				for (int k = i + 1; k <= j; k++) {
					min = Math.min(min, arr[k]);
				}
				ans += min;
			}
		}
		return ans;
	}

	// 没有用单调栈
	public static int subArrayMinSum2(int[] arr) {
		// left[i] = x : arr[i]左边,离arr[i]最近,<=arr[i],位置在x
		int[] left = leftNearLessEqual2(arr);
		// right[i] = y : arr[i]右边,离arr[i]最近,< arr[i],的数,位置在y
		int[] right = rightNearLess2(arr);
		int ans = 0;
		for (int i = 0; i < arr.length; i++) {
			int start = i - left[i]; //i位置左边能扩充的范围中的数字个数
			int end = right[i] - i; //i位置右边能扩充的范围中的数字个数
			ans += start * end * arr[i];
		}
		return ans;
	}

	public static int[] leftNearLessEqual2(int[] arr) {
		int N = arr.length;
		int[] left = new int[N];
		for (int i = 0; i < N; i++) {
			int ans = -1;
			for (int j = i - 1; j >= 0; j--) {
				if (arr[j] <= arr[i]) {
					ans = j;
					break;
				}
			}
			left[i] = ans;
		}
		return left;
	}
	
	// 比如两个位置的3,后一个位置的3往左边扩充的区域到前一个位置的3停止(到不了)
	public static int[] rightNearLess2(int[] arr) {
		int N = arr.length;
		int[] right = new int[N];
		for (int i = 0; i < N; i++) {
			int ans = N;
			for (int j = i + 1; j < N; j++) {
				if (arr[i] > arr[j]) {
					ans = j;
					break;
				}
			}
			right[i] = ans;
		}
		return right;
	}

    //O(n)
	public static int sumSubarrayMins(int[] arr) {
		int[] stack = new int[arr.length];
		int[] left = nearLessEqualLeft(arr, stack);//每个位置左边离它最近 <= 它的位置
		int[] right = nearLessRight(arr, stack); 每个位置右边离它最近 < 它的位置
		long ans = 0;
		for (int i = 0; i < arr.length; i++) {
			long start = i - left[i];
			long end = right[i] - i;
			ans += start * end * (long) arr[i];
			ans %= 1000000007;
		}
		return (int) ans;
	}

	public static int[] nearLessEqualLeft(int[] arr, int[] stack) {
		int N = arr.length;
		int[] left = new int[N];
		int size = 0;
		for (int i = N - 1; i >= 0; i--) {
			while (size != 0 && arr[i] <= arr[stack[size - 1]]) {
				left[stack[--size]] = i;
			}
			stack[size++] = i;
		}
		while (size != 0) {
			left[stack[--size]] = -1;
		}
		return left;
	}

	public static int[] nearLessRight(int[] arr, int[] stack) {
		int N = arr.length;
		int[] right = new int[N];
		int size = 0;
		for (int i = 0; i < N; i++) {
			while (size != 0 && arr[stack[size - 1]] > arr[i]) {
				right[stack[--size]] = i;
			}
			stack[size++] = i;
		}
		while (size != 0) {
			right[stack[--size]] = N;
		}
		return right;
	}

	public static int[] randomArray(int len, int maxValue) {
		int[] ans = new int[len];
		for (int i = 0; i < len; i++) {
			ans[i] = (int) (Math.random() * maxValue) + 1;
		}
		return ans;
	}

	public static void printArray(int[] arr) {
		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}

	public static void main(String[] args) {
		int maxLen = 100;
		int maxValue = 50;
		int testTime = 100000;
		System.out.println("测试开始");
		for (int i = 0; i < testTime; i++) {
			int len = (int) (Math.random() * maxLen);
			int[] arr = randomArray(len, maxValue);
			int ans1 = subArrayMinSum1(arr);
			int ans2 = subArrayMinSum2(arr);
			int ans3 = sumSubarrayMins(arr);
			if (ans1 != ans2 || ans1 != ans3) {
				printArray(arr);
				System.out.println(ans1);
				System.out.println(ans2);
				System.out.println(ans3);
				System.out.println("出错了!");
				break;
			}
		}
		System.out.println("测试结束");
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/50337.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Paper】2022_离散时间多智能体系统编队-包围控制研究_李博凡

离散时间多智能体系统编队-包围控制研究_李博凡 文章目录第四章 基于间歇控制的离散时间多智能体系统编队-包围控制4.1 引言4.2 基于状态反馈的离散时间间歇多智能体系统编队-包围控制4.2.1 模型描述4.2.2 稳定性分析4.3 基于观测器的离散时间间歇多智能体系统编队-包围控制4.3…

2022最全Hbuilder打包成苹果IOS-App的详解

本文相关主要记录一下使用Hbuilder打包成苹果IOS-App的详细步骤。 介绍一下个人开发者账号&#xff1a; 再说下什么是免费的苹果开发者账号&#xff0c;就是你没交688年费的就是免费账号&#xff0c;如果你想变成付费开发者账号&#xff0c;提交申请付费就行&#xff0c;账号都…

qt中Qtcpserver服务端_qt websocket

0.前言 本文主要讲解 Qt TCP 相关接口的基本应用&#xff0c;一些实践相关的后面会单独写。 TCP 协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP 通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输。 TCP 通过三次握手来…

Feign的使用

1、Feigin接口&#xff1a; ProductClientService import com.mengxuegu.springcloud.entities.Product; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.anno…

SIMetrix导入MOS管SPICE参数进行仿真的快速方法

问题的提出 在采用SIMetrix 8.3软件进行E类放大器的仿真过程中&#xff0c;用到了NEXPERIA公司的NMOS管器件PMH550UNE, 但在SIMetrix 8.3的库中没有该器件&#xff0c;因此需要导入第三方库文件. 通常的办法是从生产该器件的公司网站上下载器件库文件&#xff0c;导入到SIMet…

MKS上游和下游集成式压力控制器的技术分析及其替代解决方案

摘要&#xff1a;目前的MKS系列集成式压力控制器本质上是一种流量调节和测量装置&#xff0c;无法直接用来进行准确的压力控制&#xff0c;而且MKS压力控制器还存在测量精度不高、压力控制范围有限和对工作介质洁净度要求很高的不足。为此&#xff0c;为了弥补这些不足&#xf…

Java 并发编程之ConcurrentHashMap源码详解

Java 并发编程之ConcurrentHashMap原理详解 文章目录Java 并发编程之ConcurrentHashMap原理详解原理剖析源码剖析一、构造方法分析二、初始化三、put()实现分析四、扩容原理剖析 HashMap通常的实现方式是“数组链表”&#xff0c;这种方式被称为“拉链法”。 ConcurrentHashMa…

K_A08_001 基于 STM32等单片机驱动L298N模块按键控制直流电机启停正反转加减速

目录 一、资源说明 二、基本参数 1、参数 2、引脚说明 三、驱动说明 L298N模块驱动时序 对应程序: ENA ENB输出PWM 四、部分代码说明 接线说明 1、STC89C52RCL298N模块 2、STM32F103C8T6L298N模块 五、基础知识学习与相关资料下载 六、视频效果展示与程序资料获取 七、项…

宇视雷视工勘指导(卡口电警篇)

雷视工勘指导&#xff08;卡口电警篇&#xff09; 卡口和电子警察具备车辆信息采集、违法驾驶行为检测等功能&#xff0c;是城市道路交通治理的利器。为了提升现场工勘效率并保障工勘的质量&#xff0c;本次为大家介绍一款宇视的前端工勘神器——《宇视智能交通工勘计算表》&a…

SpringMVC-全面详解(学习总结---从入门到深化)

目录 SpringMVC简介 MVC模型 SpringMVC SpringMVC入门案例 SpringMVC执行流程 SpringMVC的组件 组件的工作流程 SpringMVC参数获取_封装为简单数据类型 SpringMVC参数获取_封装为对象类型 封装单个对象 封装关联对象 SpringMVC参数获取_封装为集合类型 封装为Lis…

Qt报错总结

转载 Qt报错 widget.obj的问题 遇到这种情况可能是链接出错造成的&#xff0c;所以需要首选就是需要将生成的bulid文件进行删除&#xff0c;然后运行&#xff0c;基本可以了 补充知识&#xff1a; QtCreator中qmake、构建、运行、清理等区别与联系 qt执行流程&#xff1a;qma…

模式也能开盲盒,”盲返“模式带动电商平台共享经济

今年元月份&#xff0c;国务院也是提出消费返利、消费优惠、利润分享属于电商平台共享经济的促销模式&#xff0c;属于合法合规的新业态经济以及新零售重大变革的突破&#xff0c;全民参与共同富裕。 而最近市场上出了个很火的电商模式——消费盲返&#xff0c;是一个针对每个…

MacBook Pro 耗电严重的终极解决办法2022年

背景&#xff1a; 最近在用mac时发现一个问题&#xff0c;合上盖子之后&#xff0c;明天打开&#xff0c;没有插电源的情况下&#xff0c;就会没电&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;非常影响使用&#xff0c;最后才发现是…

第十一章:Java对象内存布局和对象头

对象内存布局对象头运行时元数据类型指针&#xff08;类元数据&#xff09;实例数据对齐填充对象内存布局之JOL 证明GC分代年龄说明压缩指针参数对象内存布局 兄弟们感兴趣的话&#xff0c;在 JVM 篇有对 对象的详细介绍&#xff1a;对象实例化内存布局 对象头 运行时元数据 …

datax-hdfsReader 学习

今天同事遇到了一个问题。 就是hdfsreader->mysqlwriter这种的时候。 有的分区没有数据会报错。 com.tencent.s2.dataingestion.common.exception.DataXException: Code:[HdfsReader-08], Description:[您尝试读取的文件目录为空.]. - 未能找到待读取的文件,请确认您的配…

持久层框架设计实现及MyBatis源码分析 ---- MyBatis基础回顾及高级应用

一、基本应用 基本开发步骤&#xff1a; ① 添加MyBatis的坐标 ② 创建xxx数据表 ③ 编写Xxx实体类 ④ 编写sql映射⽂件XxxMapper.xml ⑤ 编写核⼼配置⽂件SqlMapConfig.xml ⑥ 编写测试类 二、配置文件介绍 1. sql映射配置文件 XxxMapper.xml (1) 基础使用 (2) 动态SQL w…

卡尔曼滤波:The Scaler Kalman Filter常量卡尔曼滤波器

本文是Quantitative Methods and Analysis: Pairs Trading此书的读书笔记。 估计一个常数的通常做法是&#xff0c;做多次测量&#xff08;measurement)&#xff0c;然后使用测量的平均值作为估计值。从统计学的思想上来说&#xff0c;这种做法可以尽量减小估计的误差。这种方…

SpringBoot+Vue实现前后端分离的宠物医院管理系统

文末获取源码 开发语言&#xff1a;Java 使用框架&#xff1a;spring boot 前端技术&#xff1a;JavaScript、Vue.js 、css3 开发工具&#xff1a;IDEA/MyEclipse/Eclipse、Visual Studio Code 数据库&#xff1a;MySQL 5.7/8.0 数据库管理工具&#xff1a;phpstudy/Navicat JD…

Fiddler抓包工具是最强大最好用的 Web 调试工具之一

Fiddler是最强大最好用的Web调试工具之一&#xff0c;它能记录所有客户端和服务器的http和https请求&#xff0c;允许你监视&#xff0c;设置断点&#xff0c;甚至修改输入输出数据. 使用Fiddler无论对开发还是测试来说&#xff0c;都有很大的帮助。 目录 Fiddler的基本介绍 …

【C++11重点语法上】lambda表达式,初始化列表

目录 引子&#xff1a;C11为什么的源来 语法1&#xff1a;初始化列表 1.2.2 多个对象的列表初始化 语法3&#xff1a;默认成员函数控制&#xff08;delete&#xff0c;default&#xff09; 语法4&#xff1a;lambda表达式 引子&#xff1a;C11为什么的源来 在2003年C标准…