代码随想录算法训练营第五十九天| 503.下一个更大元素II,42. 接雨水

news2024/10/9 12:31:02

代码随想录算法训练营第五十九天| 503.下一个更大元素II,42. 接雨水

  • 503.下一个更大元素II
  • 42. 接雨水
    • 暴力解法
    • 双指针优化
    • 单调栈解法

503.下一个更大元素II

题目链接
视频讲解
给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素
数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数,如果不存在,则输出 -1

输入: nums = [1,2,1]
输出: [2,-1,2]

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

class Solution {
public:
    vector<int> nextGreaterElements(vector<int>& nums) {
        vector<int> result(nums.size(), -1);
        if (nums.size() == 0) return result;
        stack<int> st;
        st.push(0);
        for (int i = 1; i < nums.size() * 2; i++) { 
            // 模拟遍历两边nums,注意一下都是用i % nums.size()来操作
            if (nums[i % nums.size()] < nums[st.top()]) st.push(i % nums.size());
            else if (nums[i % nums.size()] == nums[st.top()]) st.push(i % nums.size()); 
            else {
                while (!st.empty() && nums[i % nums.size()] > nums[st.top()]) {
                    result[st.top()] = nums[i % nums.size()];
                    st.pop();
                }
                st.push(i % nums.size());
            }
        }
        return result;
    }
};

42. 接雨水

题目链接
视频讲解
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水
在这里插入图片描述

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6

本文深度讲解如下三种方法:
双指针法
动态规划
单调栈

暴力解法

本题暴力解法也是也是使用双指针
首先要明确,要按照行来计算,还是按照列来计算
按照行来计算如图:
在这里插入图片描述
按照列来计算如图:
在这里插入图片描述
在实现的时候,很容易一会按照行来计算一会按照列来计算,这样就会越写越乱,个人倾向于按照列来计算,比较容易理解,接下来看一下按照列如何计算
首先,如果按照列来计算的话,宽度一定是1了,我们再把每一列的雨水的高度求出来就可以了,可以看出每一列雨水的高度,取决于,该列 左侧最高的柱子和右侧最高的柱子中最矮的那个柱子的高度
这句话可以有点绕,来举一个理解,例如求列4的雨水高度,如图:
在这里插入图片描述
列4 左侧最高的柱子是列3,高度为2(以下用lHeight表示)
列4 右侧最高的柱子是列7,高度为3(以下用rHeight表示)
列4 柱子的高度为1(以下用height表示)
那么列4的雨水高度为 列3和列7的高度最小值减列4高度,即: min(lHeight, rHeight) - height
列4的雨水高度求出来了,宽度为1,相乘就是列4的雨水体积了
此时求出了列4的雨水体积
一样的方法,只要从头遍历一遍所有的列,然后求出每一列雨水的体积,相加之后就是总雨水的体积了
首先从头遍历所有的列,并且要注意第一个柱子和最后一个柱子不接雨水,代码如下:

for (int i = 0; i < height.size(); i++) {
    // 第一个柱子和最后一个柱子不接雨水
    if (i == 0 || i == height.size() - 1) continue;
}

在for循环中求左右两边最高柱子,代码如下:

int rHeight = height[i]; // 记录右边柱子的最高高度
int lHeight = height[i]; // 记录左边柱子的最高高度
for (int r = i + 1; r < height.size(); r++) {
    if (height[r] > rHeight) rHeight = height[r];
}
for (int l = i - 1; l >= 0; l--) {
    if (height[l] > lHeight) lHeight = height[l];
}

最后,计算该列的雨水高度,代码如下:
int h = min(lHeight, rHeight) - height[i];
if (h > 0) sum += h; // 注意只有h大于零的时候,在统计到总和中
整体代码如下:

class Solution {
public:
    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 rHeight = height[i]; // 记录右边柱子的最高高度
            int lHeight = height[i]; // 记录左边柱子的最高高度
            for (int r = i + 1; r < height.size(); r++) {
                if (height[r] > rHeight) rHeight = height[r];
            }
            for (int l = i - 1; l >= 0; l--) {
                if (height[l] > lHeight) lHeight = height[l];
            }
            int h = min(lHeight, rHeight) - height[i];
            if (h > 0) sum += h;
        }
        return sum;
    }
};

因为每次遍历列的时候,还要向两边寻找最高的列,所以时间复杂度为O(n^2),空间复杂度为O(1)
力扣后面修改了后台测试数据,所以以上暴力解法超时了

双指针优化

在暴力解法中,我们可以看到只要记录左边柱子的最高高度 和 右边柱子的最高高度,就可以计算当前位置的雨水面积,这就是通过列来计算
当前列雨水面积:min(左边柱子的最高高度,记录右边柱子的最高高度) - 当前柱子高度
为了得到两边的最高高度,使用了双指针来遍历,每到一个柱子都向两边遍历一遍,这其实是有重复计算的。我们把每一个位置的左边最高高度记录在一个数组上(maxLeft),右边最高高度记录在一个数组上(maxRight),这样就避免了重复计算
当前位置,左边的最高高度是前一个位置的左边最高高度和本高度的最大值
即从左向右遍历:maxLeft[i] = max(height[i], maxLeft[i - 1]);
从右向左遍历:maxRight[i] = max(height[i], maxRight[i + 1]);
代码如下:

class Solution {
public:
    int trap(vector<int>& height) {
        if (height.size() <= 2) return 0;
        vector<int> maxLeft(height.size(), 0);
        vector<int> maxRight(height.size(), 0);
        int size = maxRight.size();

        // 记录每个柱子左边柱子最大高度
        maxLeft[0] = height[0];
        for (int i = 1; i < size; i++) {
            maxLeft[i] = max(height[i], maxLeft[i - 1]);
        }
        // 记录每个柱子右边柱子最大高度
        maxRight[size - 1] = height[size - 1];
        for (int i = size - 2; i >= 0; i--) {
            maxRight[i] = max(height[i], maxRight[i + 1]);
        }
        // 求和
        int sum = 0;
        for (int i = 0; i < size; i++) {
            int count = min(maxLeft[i], maxRight[i]) - height[i];
            if (count > 0) sum += count;
        }
        return sum;
    }
};

单调栈解法

关于单调栈的理论基础,单调栈适合解决什么问题,单调栈的工作过程,大家可以先看这题讲解 739. 每日温度
单调栈就是保持栈内元素有序,和栈与队列:单调队列一样,需要我们自己维持顺序,没有现成的容器可以用
通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了
而接雨水这道题目,我们正需要寻找一个元素,右边最大元素以及左边最大元素,来计算雨水面积
准备工作
那么本题使用单调栈有如下几个问题:
首先单调栈是按照行方向来计算雨水,如图:
在这里插入图片描述
知道这一点,后面的就可以理解了
使用单调栈内元素的顺序
从大到小还是从小到大呢?
从栈头(元素从栈头弹出)到栈底的顺序应该是从小到大的顺序
因为一旦发现添加的柱子高度大于栈头元素了,此时就出现凹槽了,栈头元素就是凹槽底部的柱子,栈头第二个元素就是凹槽左边的柱子,而添加的元素就是凹槽右边的柱子
如图:
在这里插入图片描述
关于单调栈的顺序给大家一个总结: 739. 每日温度中求一个元素右边第一个更大元素,单调栈就是递增的,84.柱状图中最大的矩形求一个元素右边第一个更小元素,单调栈就是递减的
遇到相同高度的柱子怎么办
遇到相同的元素,更新栈内下标,就是将栈里元素(旧下标)弹出,将新元素(新下标)加入栈中
例如 5 5 1 3 这种情况,如果添加第二个5的时候就应该将第一个5的下标弹出,把第二个5添加到栈中
因为我们要求宽度的时候 如果遇到相同高度的柱子,需要使用最右边的柱子来计算宽度
如图所示:
在这里插入图片描述
栈里要保存什么数值
使用单调栈,也是通过 长 * 宽 来计算雨水面积的
长就是通过柱子的高度来计算,宽是通过柱子之间的下标来计算,那么栈里有没有必要存一个pair<int, int>类型的元素,保存柱子的高度和下标呢
其实不用,栈里就存放下标就行,想要知道对应的高度,通过height[stack.top()] 就知道弹出的下标对应的高度了
所以栈的定义如下:
stack st; // 存着下标,计算的时候用下标对应的柱子高度
明确了如上几点,我们再来看处理逻辑。
单调栈处理逻辑
以下操作过程其实和 739. 每日温度也是一样的,建议先做 739. 每日温度
以下逻辑主要就是三种情况
情况一:当前遍历的元素(柱子)高度小于栈顶元素的高度 height[i] < height[st.top()]
情况二:当前遍历的元素(柱子)高度等于栈顶元素的高度 height[i] == height[st.top()]
情况三:当前遍历的元素(柱子)高度大于栈顶元素的高度 height[i] > height[st.top()]
先将下标0的柱子加入到栈中,st.push(0);。 栈中存放我们遍历过的元素,所以先将下标0加进来
然后开始从下标1开始遍历所有的柱子,for (int i = 1; i < height.size(); i++)
如果当前遍历的元素(柱子)高度小于栈顶元素的高度,就把这个元素加入栈中,因为栈里本来就要保持从小到大的顺序(从栈头到栈底)
代码如下:
if (height[i] < height[st.top()]) st.push(i);
如果当前遍历的元素(柱子)高度等于栈顶元素的高度,要跟更新栈顶元素,因为遇到相相同高度的柱子,需要使用最右边的柱子来计算宽度
代码如下:

if (height[i] == height[st.top()]) { // 例如 5 5 1 7 这种情况
  st.pop();
  st.push(i);
}

如果当前遍历的元素(柱子)高度大于栈顶元素的高度,此时就出现凹槽了,如图所示:
在这里插入图片描述
取栈顶元素,将栈顶元素弹出,这个就是凹槽的底部,也就是中间位置,下标记为mid,对应的高度为height[mid](就是图中的高度1)
此时的栈顶元素st.top(),就是凹槽的左边位置,下标为st.top(),对应的高度为height[st.top()](就是图中的高度2)
当前遍历的元素i,就是凹槽右边的位置,下标为i,对应的高度为height[i](就是图中的高度3)
此时大家应该可以发现其实就是栈顶和栈顶的下一个元素以及要入栈的元素,三个元素来接水!
那么雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度,代码为:int h = min(height[st.top()], height[i]) - height[mid];
雨水的宽度是 凹槽右边的下标 - 凹槽左边的下标 - 1(因为只求中间宽度),代码为:int w = i - st.top() - 1 ;
当前凹槽雨水的体积就是:h * w
求当前凹槽雨水的体积代码如下:

while (!st.empty() && height[i] > height[st.top()]) { // 注意这里是while,持续跟新栈顶元素
    int mid = st.top();
    st.pop();
    if (!st.empty()) {
        int h = min(height[st.top()], height[i]) - height[mid];
        int w = i - st.top() - 1; // 注意减一,只求中间宽度
        sum += h * w;
    }
}
class Solution {
public:
    int trap(vector<int>& height) {
        if (height.size() <= 2) return 0; // 可以不加
        stack<int> st; // 存着下标,计算的时候用下标对应的柱子高度
        st.push(0);
        int sum = 0;
        for (int i = 1; i < height.size(); i++) {
            if (height[i] < height[st.top()]) {     // 情况一
                st.push(i);
            } if (height[i] == height[st.top()]) {  // 情况二
                st.pop(); // 其实这一句可以不加,效果是一样的,但处理相同的情况的思路却变了。
                st.push(i);
            } else {                                // 情况三
                while (!st.empty() && height[i] > height[st.top()]) { // 注意这里是while
                    int mid = st.top();
                    st.pop();
                    if (!st.empty()) {
                        int h = min(height[st.top()], height[i]) - height[mid];
                        int w = i - st.top() - 1; // 注意减一,只求中间宽度
                        sum += h * w;
                    }
                }
                st.push(i);
            }
        }
        return sum;
    }
};

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

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

相关文章

腾讯云优惠券:定义、领取方法及使用教程

腾讯云作为国内知名的云计算服务提供商&#xff0c;经常为用户提供各种优惠活动&#xff0c;本文将详细介绍腾讯云优惠券的定义、领取方法及使用教程&#xff0c;助力大家轻松上云&#xff01; 一、腾讯云优惠券定义介绍 腾讯云优惠券是什么&#xff1f;腾讯云优惠券是腾讯云推…

谈一谈对string容器的赋值重载的更深一步理解

先来看一看我写的string容器&#xff08;包含了基本的构造&#xff0c;析构&#xff0c;赋值&#xff0c;拷贝&#xff09;。 class String { public://构造/*explicit*/ String(const char* arr "") {assert(arr);//判别不会传入空指针cout << "String…

markdown pdf报错问题

vscode的md文件转pdf的插件 右键可以直接转换 但是很久没用&#xff0c;现在重新使用出现了报错 ERROR: Failed to download Chromium! If you are behind a proxy, set the http.proxy option to settings.json and restart Visual Studio Code. 解决办法 首…

万字C语言之分支语句和循环语句

前言&#xff1a; &#x1f4d5;作者简介&#xff1a;热爱编程的小七&#xff0c;致力于C、Java、Python等多编程语言&#xff0c;热爱编程和长板的运动少年&#xff01; &#x1f4d8;相关专栏Java基础语法&#xff0c;JavaEE初阶&#xff0c;数据库&#xff0c;数据结构和算法…

OpenCV DNN深度学习简介

&#x1f482; 个人主页:风间琉璃&#x1f91f; 版权: 本文由【风间琉璃】原创、在CSDN首发、需要转载请联系博主&#x1f4ac; 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦 前言 本文基本上来自以下官方手册&#xff1a;https://learnopencv.com/deep-l…

9月8日扒面经

慢sql日志的排查和调优 开启慢查询日志&#xff1a;首先需要确保数据库的慢查询日志功能已经开启。在MySQL中&#xff0c;可以通过设置slow_query_log参数为1来开启慢查询日志&#xff0c;并设置long_query_time参数来定义慢查询的阈值。定位慢查询语句&#xff1a;根据慢查询…

.kann勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复

引言&#xff1a; 在数字化时代&#xff0c;勒索病毒已经成为了网络安全的严重威胁&#xff0c;其中 .kann 勒索病毒是最新的一种。这种恶意软件通过加密您的数据文件&#xff0c;然后勒索您支付赎金以获取解密密钥。本文将深入探讨 .kann 勒索病毒、如何恢复被它加密的数据文件…

【多线程】Synchronized 用法详解

Synchronized 用法详解 一. synchronized 的特性1. 互斥2. 刷新内存3. 可重入 二. synchronized 的使用1. 直接修饰普通方法2. 修饰静态方法3. 修饰代码块 三. Java 标准库中的线程安全类 synchronized: 又叫监视器锁 monitor lock一. synchronized 的特性 1. 互斥 (保证原子…

SpringBoot整合WebSocket【代码】

系列文章目录 一、SpringBoot连接MySQL数据库实例【tk.mybatis连接mysql数据库】 二、SpringBoot连接Redis与Redisson【代码】 三、SpringBoot整合WebSocket【代码】 文章目录 系列文章目录代码下载地址一、效果演示二、引入依赖三、WebSocketConfig四、SessionWrap五、WebSoc…

这样的接口千万别暴露,小心横向越权。

前言 谈不上是多么厉害的知识&#xff0c;但可能确实有人不清楚或没见过。 我还是分享一下&#xff0c;就当一个小知识点。 如果知道的&#xff0c;就随便逛逛&#xff0c;不知道的&#xff0c;Get到了记得顺手点个赞哈。 正文 1、接口别随便暴露 当一个项目的维护周期拉长的时…

zustand实践与源码阅读

如何管理数据? 日常使用&#xff1a;发布订阅、context、redux… zustand是一个轻量、快速、可扩展的状态管理库。 目前在社区非常流行&#xff0c;现在github上有30K的star。npm包的下载量&#xff0c;现在也仅次于redux&#xff0c;位于mobx之上&#xff0c;并且差距日益扩大…

客户端SDK测试是什么?如何测?

01 是什么 客户端SDK是为第三方开发者提供的软件开发工具包&#xff0c;包括SDK接口、开发文档和Demo示例等。SDK和应用之间是什么关系呢&#xff1f;以云信即时消息服务为例&#xff0c;如下图所示&#xff0c;应用客户端通过调用云信SDK接口&#xff0c;进行消息等数据查询存…

华为云云耀云服务器L实例评测 | 零门槛入门使用教学

近年来&#xff0c;随着企业应用上云&#xff0c;云服务器一直备受用户的关注。特别是对于大多数的中小企业在上云的过程中&#xff0c;都希望能使用的是一种快速、简洁高效的云服务器。因为这样能尽可能地减轻企业运维的成本&#xff0c;同时又能方便企业的信息技术人员管理。…

Set和Map及哈希表介绍

搜索方式介绍TreeMapMap使用 TreeSetSet使用 Set和Map常用方法练习(后面补充)练习之Set/Mapoj练习&#xff08;后面补充&#xff09;哈希表哈希冲突避免冲突-哈希函数设计避免冲突-负载因子调节避免冲突-闭散列避免冲突-开散列 模拟实现哈希表哈希Map源码分析 搜索方式介绍 哈…

mysql的索引分类

索引分类 在 MySQL 数据库&#xff0c;将索引的具体类型主要分为以下几类&#xff1a;主键索引、唯一索引、常规索引、全文索引。 分类 含义 特点 关键字 主键 索引 针对于表中主键创建的索引 默认自动创建 , 只能 有一个 PRIMARY 唯一 索引 避免同一个表中某数据列中…

C语言“牵手”速卖通商品详情数据方法,速卖通商品详情API接口,速卖通API申请指南

速卖通是全球最大的自营式电商企业之一&#xff0c;在线销售计算机、手机及其它数码产品、家电、汽车配件、服装与鞋类、奢侈品、家居与家庭用品、化妆品与其它个人护理用品、食品与营养品、书籍与其它媒体产品、母婴用品与玩具、体育与健身器材以及虚拟商品等。 速卖通平台的…

nginx日志、nginx访问控制、nginx优化

总结 nginx监控 内存&#xff0c;网络&#xff0c;磁盘&#xff0c;cpu 对nginx监控可以监控什么&#xff1f; 1、监控nginx服务存活状况&#xff08;ss -antpl 、systemctl status nginx 、pa aux | grep nginx&#xff09; 2、对nginx的运行状态进行监控 3、 监控nginx的监…

进程的同步与互斥

相关概念 临界资源与临界区 临界资源&#xff1a;同一时刻只能由一个进程使用的资源。 如打印机、磁带机、绘图仪等物理设备&#xff1b;由不同进程共享的消息队列、变量、数据、文件等软件资源 临界区&#xff1a;程序中访问临界资源的那一部分代码 进入区、退出区、剩余区&a…

六、MySql表的增删改查

CRUD : Create(创建), Retrieve(读取)&#xff0c;Update(更新)&#xff0c;Delete&#xff08;删除&#xff09; 文章目录 一、Create&#xff08;一&#xff09;语法&#xff08;二&#xff09;案例&#xff08;三&#xff09;插入情况1.单行数据 全列插入2.多行数据 指定…

Matlab之数组字符串函数汇总

一、前言 在MATLAB中&#xff0c;数组字符串是指由字符组成的一维数组。字符串可以包含字母、数字、标点符号和空格等字符。MATLAB提供了一些函数和操作符来创建、访问和操作字符串数组。 二、字符串数组具体怎么使用&#xff1f; 1、使用单引号或双引号括起来的字符序列 例…