【Hello Algorithm】单调栈(未完待续)

news2024/11/27 3:49:49

单调栈解决的问题

我们单调栈的提出主要是为了解决这么一个问题

现在给我们一个数组 现在要求你建立一张表 这张表中能够查询到两个信息

这两个信息分别是

  • 当前数字左边小于该数字并且下标位置最相近的下标
  • 当前数字右边小于该数字并且下标位置最相近的下标

同理 大于也可以

原本我们暴力的方法是每次遍历到需要知道前后下标的位置我们都往前或者是往后遍历出我们需要的数据 这样子的时间复杂度就是N平方

而我们单调栈的方法能做到 N 的时间复杂度

整体思路(无重复值版)

单调栈解决上面问题得思路如下

我们首先要准备一个空的栈 我们要确保这个栈从栈底到栈顶的数字由小到大

在这里插入图片描述

假设我们现在有一个数组 【3 ,4 ,2,6,1,7,0】

  • 此时由于栈里面没有数据我们的元素3 直接入栈 到下一个元素4
  • 此时由于栈里面的元素3 小于我们的元素4 符合规则 所以说元素4直接入栈 到下一个元素2
  • 此时由于栈里面的元素4 大于要插入的元素2 不符合规则 所以说元素4要出栈
  • 而在元素4出栈的一瞬间我们也就得到了我们需要的一组数组
  • 小于元素4并且位于左侧的数是3 小于元素4并且位于右侧的数是2
  • 后面的数据以此类推 … …

原理解释

  • 为什么栈下面的元素就是距离当前元素左侧最近的且小于当前数字的数

因为我们栈是严格按照从小到大的顺序进行排列的 所以说下面的元素一定小于当面的元素 所以说小于当前数字证明完毕

如果说还有其他元素距离当前元素的左侧更近并且小于当前元素 那么这个元素一定会出现在栈中这两个元素的中间

  • 为什么还未入栈的元素就是距离当前元素右侧最近且小于当前数字的数

因为我们栈是严格按照从小到大的顺序进行排列的 所以说只有比当前元素小的元素才能让当前元素出栈

如果说还有其他元素是距离当前元素的右侧更近并且小于当前元素 那么这个元素一定提前让当前元素出栈

整体思路(有重复值版)

在这里插入图片描述
有重复值版本和无重复值版本最主要的一个区别就是 有重复值版本里面存放的元素是一个链表 该链表连接着各个元素值相同的元素的下标

  • 寻找距离当前元素右侧最近并且小于该元素的值的元素步骤和无重复值一样
  • 寻找距离当前元素左侧最近并且小于该元素的值的元素时 如果下次是一个链表 则我们要选择这个链表的最后一个元素

代码表示(默认全部数字为正整数)

有重复值版

函数表示如下

vector<vector<int>> MonotonicStack(vector<int>& arr); 

返回值说明

我们返回的是一个二维数组

该二维数组的下标对应着数组中数字的下标 二维数组中的每一行有两个元素 这两个元素分别代表着距离当前元素左边(右边)最近的且小于当前元素的值 如果不存在该值 则填入 -1

参数说明

一个一维数组

无重复版代码表示如下

vector<vector<int>> MonotonicStack(vector<int>& arr)
{
  int N = static_cast<int>(arr.size());    
  vector<vector<int>> ans(N , vector<int>(2 , -1));    
  stack<int> st;    
    
  for (int i = 0; i < N; i++)    
  {    
    while(!st.empty() && arr[st.top()] > arr[i])    
    {    
      int PopIndex = st.top();    
      st.pop();    
    
      int LeftIndex = st.empty() ? -1 : st.top();                                                                                               
    
      ans[PopIndex][0] = LeftIndex;    
      ans[PopIndex][1] = i;    
    }    
    
    st.push(i);    
  }    
    
  // 此时栈中还有剩余元素未清空  
      while (!st.empty())    
  {    
    int PopIndex = st.top();    
    st.pop();    
    
    int LeftIndex = st.empty()? -1 : st.top();    
    
    ans[PopIndex][0] = LeftIndex;    
    ans[PopIndex][1] = -1; // 这里也可以不写 因为本来就是-1
  }

  return ans;
}

有重复值版

函数表示如下

vector<vector<int>> MonotonicStack(vector<int>& arr); 

返回值说明

我们返回的是一个二维数组

该二维数组的下标对应着数组中数字的下标 二维数组中的每一行有两个元素 这两个元素分别代表着距离当前元素左边(右边)最近的且小于当前元素的值 如果不存在该值 则填入 -1

参数说明

一个一维数组

有重复值版代码表示如下

vector<vector<int>> MonotonicStack(vector<int>& arr)    
{    
  int N = static_cast<int>(arr.size());    
  vector<vector<int>> ans(N , vector<int>(2 , -1));    
                                                                                                                                  
  stack<list<int>> st;    
    
  for (int i = 0; i < N ; i++)    
  {    
    while(!st.empty() && arr[st.top().front()] > arr[i])    
    {    
      list<int> popIs = st.top();    
      st.pop();    
    
      int LeftIndex = st.empty() ? -1 : st.top().back();    
      // 之后依次更新该链表中的所有位置     
      for (auto x : popIs)    
      {    
        ans[x][0] = LeftIndex;    
        ans[x][1] = i;    
      }    
    }    
    
    if (!st.empty() && arr[st.top().front()] == arr[i])    
    {    
      st.top().push_back(i);    
    }
        else // empty ||  <       
    {
      list<int> Ins = {i};
      st.push(Ins);
    }                                                                                                                             
  }

  // 更新完毕之后可能栈里面还有值 也要全部更新完毕 最后返回ans
  while(!st.empty())
  {
    list<int> popIs = st.top();
    st.pop();

    int LeftIndex = st.empty() ? -1 : st.top().back();
    // 之后依次更新该链表中的所有位置 
    for (auto x : popIs)
    {
      ans[x][0] = LeftIndex;
      ans[x][1] = -1;
    }
  }

  return ans;
}

单调栈相关题目

题目一

给定一个只包含正数的数组arr , 该数组中的任意一个子数组sub 都会有一个A指标

A指标的计算方法为 (sub数组的和)* (sub数组中的最小值)

现在要求你设计一个函数 返回arr中所有子数组中最大的A指标

注意 子数组是连续的

解题思路

解决这个问题的思路其实很简单 假设我们目前有一个数组 arr 其中A指标最大的子数组是sub

那么sub中的最小值是不是肯定是数组arr的一个数字

既然我们确定了最小值 A指标的计算公式又是 最小值 * (sub数组和)

那么我们是不是只要让以该下标作为最小值的子数组尽可能的大就好了啊

那么现在的问题就转化为了 怎么才能让这个子数组尽可能的大呢?

一个比较笨的办法就是依次往左往右遍历嘛 但是这样子时间复杂度就会很高

还有一种方式就是按照我们写的单调栈 只需要将ans数组中的下标加一减一就好了(边界问题需要自己考虑)

关于数组和的计算我们可以使用前缀和数组来简化

代码表示如下

  vector<int> arr = {3 , 1, 1 ,3 , 5, 5, 5};

  vector<vector<int>> ans = MonotonicStack(arr);
  int N = static_cast<int>(arr.size());
  //  前缀和数组
  vector<int> prearr(N , 0);
  prearr[0] = arr[0] ;
  for(int i = 1; i < N; i++)    
  {    
    prearr[i] = prearr[i - 1] + arr[i];    
  }    
    
  vector<int> max_ans(N , 0);    
  // 以数组的每个位置作为最小值 尽可能扩大数组范围 计算出结果 最后结果就在其中                 
  for (int i = 0; i < N; i++)    
  {    
    int LeftIndex = ans[i][0] == -1 ? 0  : ans[i][0] + 1;
    int RightIndex = ans[i][1] == -1 ? N - 1 : ans[i][1] - 1;
       int sub_sum = prearr[RightIndex] - prearr[LeftIndex] + arr[LeftIndex];

    max_ans[i] = sub_sum * arr[i];                                                             
  }

之后max_ans数组里面的结果就是最终答案

题目二

给定一个非负数组 arr 代表直方图 返回直方图的最大长方形面积

题目解释

直方图的含义如下 以数组 【3 , 2,4 ,2 , 1】为例 如下图

在这里插入图片描述

每个下标代表着当前位置的告诉 最终他们组成了一个图形

现在要你求这个图形中 最大的长方形面积

解题思路

其实这个问题和上个问题的思路完全一致

我们只需要遍历数组中每一个元素 然后尽可能的让这个长方形变大即可

代码类似于题目一 这里就不提供了

题目三

给定一个二维数组 matrix 其中的值不是0就是1

返回由1组成的一个最大子矩阵 给出其中1的数量

题目解释

给出一个二维数组图 matrix

在这里插入图片描述

我们的子矩阵中只能包含1 现在要求你在图中找出一个最大的子矩阵 并且返回1的个数

解题思路

如果我们使用纯暴力的方法去解这道题的话 它的时间复杂度就是N的四次方

(计算方式为 我们在二维表中任意选一点的时间复杂度为n平方 而两点可以组成一个矩阵 所以说时间复杂度为n的四次方)

我们正常的解题思路如下

既然要求的是在matrix中的一个子矩阵 那么该矩阵的 “地基”是不是肯定是matrix中的某一行啊

那么只要我们依次算出以matrix中每一行作为地基时最大的矩阵即可

代码思路

我们以matrix的列数为大小创建一个数组

该数组中更新以每行作为地基时的直方图 以下图为例 更新的直方图数组为

在这里插入图片描述

1 1 1 0 1
0 2 2 1 0
1 3 3 2 1
2 4 0 0 2
0 0 1 1 3
1 1 0 0 4
2 2 0 0 5

接下来的问题就转化为了计算上面这些直方图中矩形面积的最大值 类似题目二

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

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

相关文章

海康设备接入安防监控系统EasyCVR平台实现语音对讲的必要操作步骤

安防监控系统EasyCVR平台可拓展性强、视频能力灵活&#xff0c;平台可提供视频监控直播、云端录像、云存储、录像检索与回看、智能告警、平台级联、云台控制、语音对讲、智能分析接入等功能。其中&#xff0c;在语音对讲方面&#xff0c;EasyCVR平台目前可兼容海康设备的对讲。…

Vue3弹性布局(Flex)

效果如下图&#xff1a;在线预览 APIs 参数说明类型默认值必传width区域总宽度string | number‘auto’falseverticalflex 主轴的方向是否垂直&#xff0c;vertical 使用 flex-direction: columnbooleanfalsefalsewrap设置元素单行显示还是多行显示&#xff1b;参考 flex-wrap…

Word或者WPS批量调整文中图片大小的快捷方法

文章目录 0、前言1、编写宏代码2、在文档中调用宏实现一键批量调整3、就这么简单&#xff01; 0、前言 不知道大家是不是也和我一样&#xff0c;经常需要在编写的Word&#xff08;或者WPS&#xff09;文档里插入大量的图片&#xff0c;但是这些图片的尺寸大小一般都不一样&…

Accelerate 0.24.0文档 二:DeepSpeed集成

文章目录 一、 DeepSpeed简介二、DeepSpeed集成&#xff08;Accelerate 0.24.0&#xff09;2.1 DeepSpeed安装2.2 Accelerate DeepSpeed Plugin2.2.1 ZeRO Stage-22.2.2 ZeRO Stage-3 with CPU Offload2.2.3 accelerate launch参数 2.3 DeepSpeed Config File2.3.1 ZeRO Stage-…

Git 安装配置

目录 Linux 平台上安装 Debian/Ubuntu Centos/RedHat 源码安装 Windows 平台上安装 Mac 平台上安装 Git 配置 用户信息 文本编辑器 差异分析工具 查看配置信息 在使用Git前我们需要先安装 Git。Git 目前支持 Linux/Unix、Solaris、Mac和 Windows 平台上运行。 Git …

简单描述下微信小程序的相关文件以及类型?

目录 前言 相关文件类型 1. JSON 配置文件 2. WXML 文件 3. WXSS 文件 4. JavaScript 文件 图片、音频、视频等资源文件 小程序配置文件&#xff08;project.config.json&#xff09; 理解 优缺点 优点&#xff1a; 缺点&#xff1a; 总结 结尾 前言 微信小程序…

工作流引擎是什么?

工作流引擎是用来实现工作流的一种组件化工具&#xff0c;它是一整套解决方案&#xff0c;比如说一般工作流引擎包含这些功能&#xff1a;流程节点管理、流向管理等&#xff0c;是为了减小开发成本而推出的。因为在软件开发过程中&#xff0c;如果是从零开始实现工作流&#xf…

火车头采集器如何设置代理IP

火车头采集器作为一种强大的数据抓取工具&#xff0c;已经被很多人熟知&#xff0c;它最大的优势就是设置代理IP确保采集过程的顺利进行。 今天我们就来说说&#xff0c;火车头采集器是怎么设置代理IP的。 1.打开火车头采集器软件&#xff0c;在打开的界面中点击http二级代理…

间歇性工作的时钟波形对行sdc约束怎么写

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 如下图&#xff0c;紫色部分波形间歇式工作&#xff0c;如果要写约束应该怎么写&#xff1f; 答&#xff1a;按照最小周期写即可&#xff0c;只看active的部分&#xff0c;至于…

python的高性能web应用的开发与测试实验

引言 python语言一直以开发效率高著称&#xff0c;被广泛地应用于自动化领域&#xff1a; 测试自动化运维自动化构建发布自动化 但是因为其也具有如下两个特征&#xff1a; 解释型语言GIL全局解释器锁 前者导致其性能天然就被编译型语言在性能上落后了许多。而后者则在多核…

使用Docker本地安装部署Drawio绘图工具并实现公网访问

目录 前言 1. 使用Docker本地部署Drawio 2. 安装cpolar内网穿透工具 3. 配置Draw.io公网访问地址 4. 公网远程访问Draw.io 前言 提到流程图&#xff0c;大家第一时间可能会想到Visio&#xff0c;不可否认&#xff0c;VIsio确实是功能强大&#xff0c;但是软件为收费&…

Linux常用操作命令和命令行编辑快捷键

文章目录 终端快捷键 :窗口操作快捷键文件浏览器grep和管道符 终端快捷键 : Ctrl a/Home 切换到命令行开始Ctrl e/End 切换到命令行末尾 Ctrl u 清除剪切光标之前的内容Ctrl k 剪切清除光标之后的内容Ctrl y 粘贴刚才所删除的字符Ctrl r 在历史命令中查找 &#xff08;这…

Qt 之自定义控件(开关按钮)

Qt 之自定义控件&#xff08;开关按钮&#xff09; 原理源码运行结果 接触过IOS系统的童鞋们应该对开关按钮很熟悉&#xff0c;在设置里面经常遇到&#xff0c;切换时候的滑动效果比较帅气。 通常说的开关按钮&#xff0c;有两个状态&#xff1a;on、off。 下面&#xff0c;我们…

Live800:高效工作,客服人必学的10种时间效率管理术

客服人员是企业与客户沟通的桥梁&#xff0c;需要在繁忙的工作环节中保持高效率。只有提高时间效率才能更好地服务客户&#xff0c;满足客户的需求&#xff0c;提升客户满意度。因此&#xff0c;客服人员需要掌握时间效率管理术来提高工作效率。 1、制定工作计划 在开始工作之…

C#中数组、ArrayList与List对象的区别及使用场景

在C#编程中&#xff0c;数组、ArrayList和List对象是常用的数据结构和容器。它们在存储和管理数据方面都有各自的特点和用途。本文将深入探讨这三者的区别&#xff0c;并通过实际的代码示例来说明它们的使用场景和优缺点。 目录 1.数组特点使用场景 2.ArrayList特点使用场景 3.…

eVTOL分布式电推进(DEP)动力测试系统

产品简介 分布式电推进&#xff08;DEP&#xff09;技术因其灵活多变的机械电气化设计&#xff0c;可以大大提升动力系统的安全性冗余&#xff0c;极大增强飞行过程中的可操控性&#xff0c;同时可以有效降低本机噪音&#xff0c;最大限度提升动力系统的能源使用效率等优势&am…

原生应用与hybrid app开发的流程区别

Hybrid App&#xff08;混合 App&#xff09;已经成为大家接触最为广泛的 App 形式&#xff0c;不管是我们用到的微信、支付宝还是淘宝、京东等大大小小的应用都非常热衷于Hybrid App 带来的研发效率提升和灵活性。 但我们正式进入到 hybrid App 的讨论前&#xff0c;有必要先…

C++:map和set的封装原理

文章目录 红黑树的封装map和set的封装红黑树迭代器的实现operator 和 -- 的实现的实现过程 迭代器的其他模块 整体实现 本篇写于红黑树模拟实现后&#xff0c;对map和set进行封装&#xff0c;模拟实现map和set内部的原理 首先&#xff0c;map和set的底层逻辑是红黑树&#xf…

【Apache Doris】审计日志插件 | 快速体验

【Apache Doris】审计日志插件 | 快速体验 一、 环境信息1.1 硬件信息1.2 软件信息 二、 审计日志插件介绍三、 快速 体验3.1 AuditLoader 配置3.1.1 下载 Audit Loader 插件3.1.2 解压安装包3.1.3 修改 plugin.conf 3.2 创建库表3.3 初始化3.4 验证 一、 环境信息 1.1 硬件信…

识别伪装IP的网络攻击方法

识别伪装IP的网络攻击可以通过以下几种方法&#xff1a; 观察IP地址的异常现象。攻击者在使用伪装IP地址进行攻击时&#xff0c;往往会存在一些异常现象&#xff0c;如突然出现的未知IP地址、异常的流量等。这些现象可能是攻击的痕迹&#xff0c;需要对此加以留意。 检查网络通…