代码随想录59——单调栈:503下一个更大元素II、42接雨水

news2025/8/7 3:08:39

文章目录

  • 1.503下一个更大元素II
    • 1.1.题目
    • 1.2.解答
  • 2.42接雨水
    • 2.1.题目
    • 2.2.解答
      • 2.2.1.双指针for循环解法
      • 2.2.3.单调栈解法

1.503下一个更大元素II

参考:代码随想录,503下一个更大元素II;力扣题目链接

1.1.题目

在这里插入图片描述

1.2.解答

做本题之前建议先做 739. 每日温度 (opens new window) 和 496.下一个更大元素 I。

这道题和739. 每日温度也几乎如出一辙,不同的是本题要循环数组了。

相信不少同学看到这道题,就想那我直接把两个数组拼接在一起,然后使用单调栈求下一个最大值不就行了!这样思路确实是对的。

注意:为什么说把两个数组拼到一起结果就是求了一个循环的数组。因为假设最后一个数字,在它前面有比他大的数字,但是如果不是循环数组,那么由于它是最后一个数字,他右边就没有比他大的数字了。但是现在如果再把数组在后面拼接一次,就相当于最后一个数字又从开始往后去寻找第一个比他大的数字了,因此这样就形成了一个循环的效果。

将两个nums数组拼接在一起,使用单调栈计算出每一个元素的下一个最大值,最后再把结果集即result数组resize到原数组大小就可以了

代码如下

// 版本一
class Solution {
public:
    vector<int> nextGreaterElements(vector<int>& nums) {
        // 拼接一个新的nums
        vector<int> nums1(nums.begin(), nums.end());
        nums.insert(nums.end(), nums1.begin(), nums1.end());
        // 用新的nums大小来初始化result
        vector<int> result(nums.size(), -1);
        if (nums.size() == 0) return result;

        // 开始单调栈
        stack<int> st;
        for (int i = 0; i < nums.size(); i++) {
            while (!st.empty() && nums[i] > nums[st.top()]) {
                result[st.top()] = nums[i];
                st.pop();
            }
            st.push(i);
        }
        // 最后再把结果集即result数组resize到原数组大小
        result.resize(nums.size() / 2);
        return result;
    }
};

这种写法确实比较直观,但做了很多无用操作,例如修改了nums数组,而且最后还要把result数组resize回去。resize倒是不费时间,是O(1)的操作,但扩充nums数组相当于多了一个O(n)的操作。

其实也可以不扩充nums,而是在遍历的过程中模拟走了两边nums。这种方式的代码如下,其中用i % nums.size()来保证数组不越界,也就是重新从头开始访问数组中的元素。

vector<int> nextGreaterElements(vector<int> &nums)
{
    vector<int> result(nums.size(), -1);   // 定义最终结果并初始化成-1
    if(nums.empty())
        return result;
    stack<int> st;   // 单调栈
    st.push(0);   // 先把第一个数的索引加进去

    // 开始数组:这里遍历两次,表示循环数组
    for(int i = 1; i < 2*nums.size(); i++)
    {
        int cur = i % nums.size();
        // 如果当前数字 < 栈顶数组,则满足单调栈要求,可以入栈
        if(nums[cur] <= nums[st.top()])
        {
            st.push(cur);
        }
        else
        {
            while(!st.empty() && nums[cur] > nums[st.top()])
            {
                result[st.top()] = nums[cur];   // 记录结果
                st.pop();   // 出栈
            }
            st.push(cur);
        }
    }

    return result;
}

注意:编程中遇到栈越界的错误,因为自己写的过程中一直用到cur的索引和st.top()的值,所以就定义了两个变量把他们存起来了,其中cur就是当前遍历的元素的索引,这个在一次for循环中保持不变,所以没什么问题。

// 开始数组:这里遍历两次,表示循环数组
for(int i = 1; i < 2*nums.size(); i++)
{
    int cur = i % nums.size();
    int top = st.top();
    // 如果当前数字 < 栈顶数组,则满足单调栈要求,可以入栈
    if(nums[cur] <= nums[top])
    {
        st.push(cur);
    }
    else
    {
        while(!st.empty() && nums[cur] > nums[top])
        {
            result[top] = nums[cur];   // 记录结果
            st.pop();   // 出栈
            // 注意:问题就出在这里,因为此时弹出之后栈可能为空了,导致内存越界错误
            top = st.top();   // 更新栈顶的索引  
        }
        st.push(cur);
    }
}

但是top的变量在while循环中会st.pop(),然后为了更新栈顶的元素值,会写top = st.top(),此时就犯了栈溢出的错误,因为上次弹出之后,可能栈为空了,此时调用st.pop()就会报如下的错误:

在这里插入图片描述
因此,对于栈的使用,最好还是一直坚持用st.top()来访问,尤其是涉及到栈的弹出的时候。

2.42接雨水

参考:代码随想录,42接雨水;力扣题目链接

2.1.题目

在这里插入图片描述

2.2.解答

这部分有三种解法,分别是双指针for循环解法、动态规划解法和单调栈的解法。

具体的解法在代码随想录的网站上一节讲解的非常详细了,目前只看了双指针for循环解法和单调栈的解法,分别用的是列求解和行求解的方法,其实都不太难。

具体内容去看代码随想录吧,这里不再给出了,因为感觉并不是很难。

2.2.1.双指针for循环解法

实际测试此代码在力扣上会超时。

int trap(vector<int> &height)
{
    int sum = 0;  // 最后的总和

    // 遍历每一列,计算该列能接的雨水的值
    for(int i = 0; i < height.size(); i++)
    {
        // 最左边和最右边的不能接雨水
        if(i == 0 || i == height.size() - 1)
            continue;
        
        // 记录左右的最大高度
        int l_h = height[i];    
        int r_h = height[i];

        // 寻找左侧的最大高度
        for(int l = i - 1; l >= 0; l--)
            if(height[l] > l_h)
                l_h = height[l];   

        // 寻找右侧的最大高度
        for(int r = i + 1; r < height.size(); i++)
            if(height[r] > r_h)
                r_h = height[r];
        
        // 计算这一列接的雨水,加到总和中(注意这里计算的雨水高度一定是>=0的,所以不用判断>0)
        sum = sum + min(l_h, r_h) - height[i];
    }

    return sum;
}

2.2.3.单调栈解法

int trap(vector<int> &height)
{
    int sum = 0;  // 最终结果
    if(height.empty())
        return sum;
    stack<int> st;
    st.push(0);    // 先存入第一个元素

    // 遍历所有元素
    for(int i = 1; i < height.size(); i++)
    {   
        // 1.仍然保持下降:则无法构成凹槽,继续加入单调栈
        if(height[i] < height[st.top()])
        {
            st.push(i);
        }
        // 2.持平:需要更新右边界,因为下面计算宽度w的时候需要用右边界计算宽度,
        //        所以这里把原先的高度的索引弹出,然后加入当前的索引
        else if(height[i] == height[st.top()])
        {
            st.pop();
            st.push(i);
        }
        // 3.上升:则出现凹槽,对每一个凹槽按照行来计算
        else
        {
            while(!st.empty() && height[i] > height[st.top()])
            {
                int mid = height[st.top()];   // mid就是凹槽的高度
                st.pop(); 
                // 注意这里弹出后要判断非空才继续算,比如最左边两个数是上升的,遍历到第2个数的时候
                // 满足上数条件,即i=1, st.top()=0。但是这样明显是无法接雨水的,因为左边没有了
                if(!st.empty())
                {
                    // 计算高度,是左右边界的高度的最小值 - 中间凹槽的高度
                    int h = min(height[st.top()], height[i]) - mid;
                    // 计算宽度,注意-1,因为i是右边界,st.top()是左边界,只有中间才是宽度
                    int w = i - st.top() - 1;  
                    sum += h * w;
                }
            }
            st.push(i);
        }
    }

    return sum;
}

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

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

相关文章

Spring Boot 集成freemarker模板引擎

前言 J2EE的领域中包含5大引擎&#xff0c;分别为模板引擎、流程引擎、搜索引擎、规则引擎、报表引擎。每种引擎都能解决某一方面的问题&#xff0c;模板引擎解决的是用户界面与业务数据分离&#xff0c;流程引擎解决的是驱动业务按照一定的流程执行&#xff0c;搜索引擎解决的…

局部线性分析(机器学习)

目录 局部线性嵌入&#xff08;LLE&#xff09; 局部线性嵌入&#xff08;LLE&#xff09;算法的主要步骤分为三步 效果如下 局部线性嵌入&#xff08;LLE&#xff09; 局部线性嵌入&#xff08;LLE&#xff09;是一种非线性降维算法 它能够使降维后的数据较好地保持原有流…

大学生HTML个人网页作业作品:基于html css实现围棋网页(带报告4800字)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

动静态链接动静态库制作与使用

前置知识 程序的编译与链接&#xff1a;动静态库属于程序链接阶段的概念&#xff0c;如果对程序的编译链接过程不太熟悉&#xff0c;可以先看一下着篇文章gcc&动静态链接&#xff1a;这篇文章讲解了如何在Linux环境下用gcc完成编译链接的每一步操作 链接库 在链接的过程…

Java对象内存结构和创建过程

文章目录对象的内存布局对象头Mark WordKlass Pointer实例数据对齐数据对象的创建总结对象的内存布局 我们的对象一般存储在我们的堆内存中&#xff0c;我们把实例对象可以划分为对象头&#xff0c;实例数据&#xff0c;对齐填充 对象头&#xff08;object header&#xff09…

SpringBoot+Vue项目流浪狗领养管理系统的设计与实现

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

图像运算和图像增强十

图像运算和图像增强十 图像锐化之 Sobel、Laplacian 算子实现边缘检测 &#xff08;1&#xff09;Sobel算子(一阶微分算子) Sobel算子是一种用于边缘检测的离散微分算子&#xff0c;它结合了高斯平滑和微分求导。该算子用于计算图像明暗程度近似值&#xff0c;根据图像边缘旁…

top命令应用(查看进程实时动态信息)

记录&#xff1a;321 场景&#xff1a;在CentOS 7.9操作系统上&#xff0c;top命令是查看进程实时动态信息工具。查看进程状态、进程使用内存状况、进程使用CPU状况、进程PID等。 版本&#xff1a; 操作系统&#xff1a;CentOS 7.9 1.top命令介绍 top命令&#xff0c;查看…

内存、指针与数组

C语言的指针可以当成一个特殊的数据类型&#xff08;像int一样的数据类型&#xff09;&#xff0c;可以说其唯一的作用就是为了存储地址&#xff0c;其他的都可以当作它的衍生用法。 指针的诸多功能都是基于其能直接操作指定内存空间存储的值&#xff0c;每个程序运行都会由操作…

git新建仓库提交项目代码+常用命令

一&#xff1a;新建仓库 输入一下仓库名称&#xff0c;归属和路径都是生成的不需要自己去编辑 点击创建就创建了一个新的仓库&#xff0c;下面就是仓库刚创建好的样子 二&#xff1a;向仓库里提交项目代码 首先打开你要提交的项目文件&#xff1a; 根据官方的提示去提交代码&…

Linux:shell编程2(内含:1.设置环境变量+2.位置参数变量+3.预定义变量+运算符+4.条件判断)

写在开头&#xff1a; 小技巧&#xff1a;除了赋值不加空格&#xff0c;其他的&#xff0c;例如是[ ] ()等都需要空格&#xff01; 1.设置环境变量&#xff1a; 注&#xff1a;类似于C语言全局变量 案例1&#xff1a;在/etc/profile文件中定义TOMCAT_HOME环境变量。 解释&…

洛谷 模拟 普及-

文章目录&#x1f4a5;前言&#x1f609;解题报告&#x1f4a5;一、快乐水&#x1f914;一、题意及思路:&#x1f60e;二、源码&#xff1a;&#x1f62e;三、代码分析&#xff1a;&#x1f4a5;二、漂亮的绝杀&#x1f914;一、题意及思路:&#x1f60e;二、源码&#xff1a;&…

小学生python游戏编程arcade----坦克大战2

小学生python游戏编程arcade----坦克大战2前言多摄象头显得分&#xff0c;title地图加载&#xff0c;精灵分层管理&#xff0c;移动精灵1、提示框制作1.1养眼绿色1.2 画距形提示框1.3 效果图1.4 提示框加提示2、子弹计数问题2.1 初始时给一定的子弹量2.2 发射子弹时进行控制2.3…

hevc 半像素

1 分数像素精度运动估计 物体在连续帧间的运动是连续的&#xff0c;而像素本身是离散的&#xff0c;这种现象带来了一个问题&#xff0c;当前帧中图像块的最佳参考块不一定位于参考帧的证书像素点位置&#xff0c;为了更加精确的预测当前带编码的图像块&#xff0c;有必要在非整…

海运整柜出口操作流程有哪些注意事项?

货物运输时&#xff0c;海运是一种非常常见的形式&#xff0c;根据货物的不同&#xff0c;海运也有很多形式的货物装运&#xff0c;海运整柜就是其中之一。 海运整柜大致分为20GP/40/GP/40HQ。是指只有一个发货人将整箱货物运到目的港&#xff0c;比较容易竞争。发货人负责装箱…

IntentService 源码理解

一、概述 本篇文章讲解的是分析IntentService源码并使用&#xff0c;安卓API迭代更新的太快&#xff0c;IntentService已经在Android8.0 (API 26)之后就不推荐使用了&#xff0c;在Android API 30正式弃用&#xff0c;官方建议用JobIntentService 或 WorkManager替代&#xff0…

为什么要少用全局变量

为什么要少用全局变量&#xff1f;甚至有些公司禁止用全局变量。有一个说法是这样的&#xff0c;全局变量的最佳前缀是什么&#xff1f;答&#xff1a;// 接下来就粗略说说这个问题。 1、全局变量和局部变量 &#xff08;1&#xff09;全局变量&#xff1a;定义在函数外&…

RocketMQ NameServer 概览

&#x1f34a; Java学习&#xff1a;Java从入门到精通总结 &#x1f34a; 深入浅出RocketMQ设计思想&#xff1a;深入浅出RocketMQ设计思想 &#x1f34a; 绝对不一样的职场干货&#xff1a;大厂最佳实践经验指南 &#x1f4c6; 最近更新&#xff1a;2022年11月18日 &#…

析构函数详解

析构函数1.概念与特性2.工作原理4.析构的顺序如果一个类中什么成员都没有&#xff0c;那么该类简称为空类。而空类中其实并不是真的什么都没有&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。构造函数&#xff1a;主要完成初始化工作析构函…

内网渗透神器CobaltStrike之配置与基础操作(一)

CobaltStrike简介 Cobalt Strike: C/S架构的商业渗透软件&#xff0c;适合多人进行团队协作&#xff0c;可模拟APT做模拟对抗&#xff0c;进行内网渗透。 Cobalt Strike 一款GUI的框架式渗透工具&#xff0c;集成了端口转发、服务扫描&#xff0c;自动化溢出&#xff0c;多模…