算法38:子数组的最小值之和(力扣907题)----单调栈

news2024/11/15 19:30:22

题目:

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

示例 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


分析:

很显然,需要分析每个子数组的最小值。而单调栈结构就可以帮助我们找到每个元素左、右侧比自己小的最近位置。也就是说,在一定范围内,数组中每个元素都可以作为子数组的最小值。

假设:

1. 假设数组为1,那么子数组最小值就为1.

2. 假设数组为{1,2} 那么子数组就可以为{1} 和 {1,2}

3. 假设数组为{1,2,3} 那么子数组就可以为{1}、{1,2} 和 {1,2,3}

总结:如果以1为子数组最小值,那么1后面出现的比1大的数,有几个数,子数组就为 n + 1。

数组{1,2,3}中,以1为最小值,那么1后面有2个数比1大,子数组就为 2 + 1 = 3个。

假设数组为{1,2,3}, 现在在最小值1前面加一个数2, 变成{2,1,2,3}.  那么子数组就为

{2,1}、{2,1,2} 、{2,1,2,3}

{1}、{1、2}、{1,2,3}

我们发现,子数组数量是原始数组{1,2,3}的2倍,即 2 * 3 = 6 个。

假设,我们在现有的数组中,前面在添加一个元素3. 变成{3,2,1,2,3}.  那么子数组为:

{3,2,1} 、{3,2,1,2} 、{3,2,1,3}

{2,1}、{2,1,2} 、{2,1,2,3}

{1}、{1、2}、{1,2,3}

我们发现,子数组数量是原始数组{1,2,3}的3倍,即 3 * 3 = 9 个.

总结:如果以1为子数组最小值,那么1前面出现的比1大的数,有几个,那么就是 (N +1)* 原始个数。

 以{3,2,1,2,3}为例子。

如果以1为最小值,1后面有2个数比1大,得到 2+1 = 3; 1前面有2个比1大,得到2+1= 3; 3*3 =9; 如果以1为最小值,那么一共有9个子数组。

如果以下标为1的2为最小值,可得 (1+1)* (0+1) = 2; 即如果以下标为1的2值为最小值,子数组有2个。即 {3,2} 和 {2}

如果以下标为3的位置的2值为最小值,前一个位置为1,即0个。前方0个比自己大的;后面1个3比自己大,可得 (0+1)*(1+1) = 2个;即{2}和{2、3}

依次类推,可以得到全部结果.

package code04.单调栈_01;

import java.util.Stack;

/**
 * 力扣力扣907题: 子数组的最小值
 *  https://leetcode.com/problems/sum-of-subarray-minimums/
 */
public class Code04_SumOfMinValueInArray {

    public int sumSubarrayMins(int[] arr)
    {
        int[][] dp = dp(arr);
        //long比int能存更长的数据
        long ans = 0;

        for (int i = 0; i < arr.length; i++) {
            //当前数
            int cur = arr[i];
            //左侧小于等于当前数的个数
            int left = i - dp[i][0];
            //右侧小于等于当前数的个数
            int right = dp[i][1] - i;

            ans +=  left * right * (long)cur;
            ans %= 1000000007;
        }

        return (int) ans;
    }

    //单调栈,统计出每个位置左、右侧比当前数小的位置
    public int[][] dp(int[] arr)
    {
        if(arr == null || arr.length == 0) {
            return null;
        }

        //当前业务需要统计出2列信息, 即左侧比自己小的位置,右侧比自己小的位置
        int[][] dp = new int[arr.length][2];
        Stack<Integer> stack = new Stack<>();
        for (int i = 0; i < arr.length; i++)
        {
            while (!stack.isEmpty() && arr[stack.peek()] > arr[i])
            {
                int cur = stack.pop();
                // -1代表不存在左侧比cur下标对应的值更小的值
                int leftIndex = stack.isEmpty() ? -1 : stack.peek();
                dp[cur][0] = leftIndex;
                dp[cur][1] = i;
            }
            //放入下标
            stack.push(i);
        }

        int rightIndex = arr.length;
        //栈中剩余元素,保持单调增
        while (!stack.isEmpty()) {
            int cur = stack.pop();
            // -1代表不存在左侧比cur下标对应的值更小的值
            int leftIndex = stack.isEmpty() ? -1 : stack.peek();
            dp[cur][0] = leftIndex;
            //因为单调增、所有右侧不存在比自己还小的值了
            dp[cur][1] = rightIndex;
        }
        return dp;
    }

    public static void main(String[] args) {

        Code04_SumOfMinValueInArray ss = new Code04_SumOfMinValueInArray();
        int[] aa = {3,1,2,4};
        System.out.println(ss.sumSubarrayMins(aa));
    }
}

虽然测试通过了,但是执行用时99ms,只击败了17%的用户,说明代码不够优秀。

技巧就是,将java原有的Stack替换成自己实现的数组。因为自己实现的数组是固定的,而Stack是需要不断经过扩容的。这样优化,效果很明显。

package code04.单调栈_01;

import java.util.Stack;

/**
 * 力扣力扣907题: 子数组的最小值
 *  https://leetcode.com/problems/sum-of-subarray-minimums/
 */
public class Code04_SumOfMinValueInArray_opt {

    public int sumSubarrayMins(int[] arr)
    {
        int[][] dp = dp(arr);
        //long比int能存更长的数据
        long ans = 0;

        for (int i = 0; i < arr.length; i++) {
            //当前数
            int cur = arr[i];
            //左侧小于等于当前数的个数
            int left = i - dp[i][0];
            //右侧小于等于当前数的个数
            int right = dp[i][1] - i;

            ans +=  left * right * (long)cur;
            ans %= 1000000007;
        }

        return (int) ans;
    }

    //单调栈,统计出每个位置左、右侧比当前数小的位置
    public int[][] dp(int[] arr)
    {
        if(arr == null || arr.length == 0) {
            return null;
        }

        //当前业务需要统计出2列信息, 即左侧比自己小的位置,右侧比自己小的位置
        int[][] dp = new int[arr.length][2];
        int[] stack = new int[arr.length];
        int stackSize = 0;
        for (int i = 0; i < arr.length; i++)
        {
            while (stackSize != 0 && arr[stack[stackSize-1]] > arr[i])
            {
                int cur = stack[--stackSize];
                // -1代表不存在左侧比cur下标对应的值更小的值
                int leftIndex = stackSize == 0 ? -1 : stack[stackSize - 1];
                dp[cur][0] = leftIndex;
                dp[cur][1] = i;
            }
            //放入下标
            stack[stackSize++] = i;
        }

        int rightIndex = arr.length;
        //栈中剩余元素,保持单调增
        while (stackSize != 0) {
            int cur = stack[--stackSize];
            // -1代表不存在左侧比cur下标对应的值更小的值
            int leftIndex = stackSize == 0 ? -1 : stack[stackSize - 1];
            dp[cur][0] = leftIndex;
            //因为单调增、所有右侧不存在比自己还小的值了
            dp[cur][1] = rightIndex;
        }
        return dp;
    }

    public static void main(String[] args) {

        Code04_SumOfMinValueInArray_opt ss = new Code04_SumOfMinValueInArray_opt();
        int[] aa = {3,1,2,4};
        int[][] dp = ss.dp(aa);

        for (int i = 0; i < dp.length; i++) {
            System.out.println("当前下标 :" + i + ", 左侧小值: " + dp[i][0] + ", 右侧小值: " + dp[i][1]);
        }
        System.out.println(ss.sumSubarrayMins(aa));
    }
}

优化完以后,效果很明显: 

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

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

相关文章

设计模式:工厂方法模式

工厂模式属于创建型模式&#xff0c;也被称为多态工厂模式&#xff0c;它在创建对象时提供了一种封装机制&#xff0c;将实际创建对象的代码与使用代码分离&#xff0c;有子类决定要实例化的产品是哪一个&#xff0c;把产品的实例化推迟到子类。 使用场景 重复代码 : 创建对象…

机器学习---可能近似正确(PAC)、出错界限框架

1. 计算学习理论概述 从理论上刻画了若干类型的机器学习问题中的困难和若干类型的机器学习算法的能力 这个理论要回答的问题是&#xff1a; 在什么样的条件下成功的学习是可能的&#xff1f; 在什么条件下某个特定的学习算法可保证成功运行&#xff1f; 这里考虑两种框架&…

【开源】基于JAVA+Vue+SpringBoot的固始鹅块销售系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 鹅块类型模块2.3 固始鹅块模块2.4 鹅块订单模块2.5 评论管理模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 鹅块类型表3.2.2 鹅块表3.2.3 鹅块订单表3.2.4 鹅块评论表 四、系统展示五、核心代码5.…

基于C语言的趣味游戏之五子棋

目录 趣味五子棋游戏 第一步 text.c文件 第二步 game.h文件 第三步 初始化 打印棋盘 玩家输入 电脑输入 判断输赢 game.c 趣味五子棋游戏 第一步 先写菜单&#xff0c;然后在主函数里调用&#xff0c;由于这是一个可以重复的游戏所以将do while循环里调用menu函数。…

C/C++ - 类的封装特性

目录 类的封装 语法格式 声明定义 分文件 访问权限 类作用域 对象模型 构造函数 默认构造函数 带参构造函数 拷贝构造函数 构造函数重载 委托构造函数 初始数据列表 构造默认参数 构造函数删除 析构函数 析构函数概念 析构函数特性 析构函数示例 析构调用…

【Unity】【游戏开发】Pico打包后项目出现运行时错误如何Debug

【背景】 开发过程中的报错可以通过控制台查看&#xff0c;但是PICO项目这类依赖特定设备环境的应用往往存在打包后在设备端发生运行时错误。这时如何能查看到Debug信息呢&#xff1f; 【分析】 Pico也是安卓系统&#xff0c;所以这个问题就可以泛化为Unity有哪些在安卓端运…

dnSpy调试工具二次开发2-输出日志到控制台

本文在上一篇文章的基础上继续操作&#xff1a; dnSpy调试工具二次开发1-新增菜单-CSDN博客 经过阅读dnSpy的源码&#xff0c;发现dnSpy使用到的依赖注入用了MEF框架&#xff0c;所以在源码中可以看到接口服务类的上面都打上了Export的特性或在构造方法上面打上ImportingConst…

力扣hot100 最小栈 变种栈

Problem: 155. 最小栈 文章目录 思路&#x1f496; Stack 自定义 Node&#x1f37b; Code 思路 &#x1f469;‍&#x1f3eb; 甜姨 &#x1f496; Stack 自定义 Node 时间复杂度: O ( 1 ) O(1) O(1) 空间复杂度: O ( n ) O(n) O(n) &#x1f37b; Code class MinS…

数据结构-顺序表的实现 [王道]

本博客记录个人寒假学习内容。此篇博客内容为 顺序表的定义。 博客中截图来自王道数据结构公开课 目录 顺序表的定义 顺序表的特点 顺序表的实现--静态分配 顺序表的实现--动态分配 顺序表的定义--知识结构框架 顺序表的定义 >线性表是具有相同(每个数据元素所占的空间…

Spring Boot使用AOP

一、为什么需要面向切面编程&#xff1f; 面向对象编程&#xff08;OOP&#xff09;的好处是显而易见的&#xff0c;缺点也同样明显。当需要为多个不具有继承关系的对象添加一个公共的方法的时候&#xff0c;例如日志记录、性能监控等&#xff0c;如果采用面向对象编程的方法&…

CSS优先级内容

定义CSS样式时&#xff0c;经常出现两个或多个样式规则应用在同一元素的情况&#xff0c;这时就会出现优先级的情况&#xff0c;那么应用的元素应该显示哪一个样式呢&#xff1f; 一.下面举例对优先级进行具体讲解。 p{color:red;} .blue{color:orange;} #header{color:blu…

OpenCV-27 Canny边缘检测

一、概念 Canny边缘检测算法是John F.Canny与1986年开发出来的一个多级边缘检测算法&#xff0c;也被很多人认为是边缘检测的最优算法。最优边缘检测的三个主要评价标准是&#xff1a; 低错频率&#xff1a;表示出尽可能多的实际边缘&#xff0c;同时尽可能的减小噪声产生的误…

Spring源码分析:refresh()

refresh()中共有13个方法&#xff0c;分别为 1.prepareRefresh() 容器刷新前的准备&#xff0c;设置上下文状态&#xff0c;获取属性&#xff0c;验证必要的属性等 protected void prepareRefresh() {//spring启动时间this.startupDate System.currentTimeMillis();//spring…

01 Redis的特性+下载安装启动+Redis自动启动+客户端连接

1.1 NoSQL NoSQL&#xff08;“non-relational”&#xff0c; “Not Only SQL”&#xff09;&#xff0c;泛指非关系型的数据库。 键值存储数据库 &#xff1a; 就像 Map 一样的 key-value 对。如Redis文档数据库 &#xff1a; NoSQL 与关系型数据的结合&#xff0c;最像关系…

Python如何获取程序打包后的目录,如何获取管理员权限

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 实现步骤 📒📝 获取程序打包后所在目录📝 获取管理员权限⚓️ 相关链接 ⚓️📖 介绍 📖 Python 是一种功能强大的编程语言,本篇文章将介绍Python如何获取程序打包后所在目录,以及如何获取管理员权限并执行需要管理…

【深度学习】sdxl中的 tokenizer tokenizer_2 区别

代码仓库&#xff1a; https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/tree/main 截图&#xff1a; 为什么有两个分词器 tokenizer 和 tokenizer_2&#xff1f; 在仔细阅读这些代码后&#xff0c;我们了解到 tokenizer_2 主要是用于 refiner 模型的。 #…

javax.servlet.http包

javax.servlet.http包 javax.srvlet.http包是对javax.servlet包的扩展。该包的类和接口处理使用HTTP进行通信的servlet。这些servlet也称为HTTP Servlet。您需要扩展HttpServlet类来开发HTTP Servlet。javax.servlet.http包经常使用的接口包括: HttpServletRequest接口HttpSe…

Windows10上通过MSYS2编译FFmpeg 6.1.1源码操作步骤

1.从github上clone代码&#xff0c;并切换到n6.1.1版本&#xff1a;clone到D:\DownLoad目录下 git clone https://github.com/FFmpeg/FFmpeg.git git checkout n6.1.1 2.安装MSYS2并编译FFmpeg源码: (1).从https://www.msys2.org/ 下载msys2-x86_64-20240113.exe &#…

x-cmd pkg | shtris - 命令行俄罗斯方块游戏

目录 简介首次用户技术特点竞品和相关作品进一步阅读 简介 shtris 是一个由 shell 脚本&#xff0c;参考 俄罗斯方块指南 (2009) 实现的俄罗斯方块游戏。 首次用户 使用 x shtris 即可自动下载并使用 在终端运行 eval "$(curl https://get.x-cmd.com)" 即可完成 x …

[TCP协议]基于TCP协议的字典服务器

目录 1.TCP协议简介: 2.TCP协议在Java中封装的类以及方法 3.字典服务器 3.1服务器代码: 3.2客户端代码: 1.TCP协议简介: TCP协议是一种有连接,面向字节流,全双工,可靠的网络通信协议.它相对于UDP协议来说有以下几点好处: 1.它是可靠传输,相比于UDP协议,传输的数据更加可靠…