【面试经典 150 | 数组】接雨水

2024-05-01 09:36
文章标签 数组 面试 经典 150 雨水

本文主要是介绍【面试经典 150 | 数组】接雨水,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 写在前面
  • Tag
  • 题目来源
  • 解题思路
    • 方法一:预处理
    • 方法二:单调栈
    • 方法三:双指针
  • 写在最后

写在前面

本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章,欢迎催更……

专栏内容以分析题目为主,并附带一些对于本题涉及到的数据结构等内容进行回顾与总结,文章结构大致如下,部分内容会有增删:

  • Tag:介绍本题牵涉到的知识点、数据结构;
  • 题目来源:贴上题目的链接,方便大家查找题目并完成练习;
  • 题目解读:复述题目(确保自己真的理解题目意思),并强调一些题目重点信息;
  • 解题思路:介绍一些解题思路,每种解题思路包括思路讲解、实现代码以及复杂度分析;
  • 知识回忆:针对今天介绍的题目中的重点内容、数据结构进行回顾总结。

Tag

【数组】【单调栈】【双指针】


题目来源

42. 接雨水


解题思路

方法一:预处理

思路

一个朴素的做法是对于数组 height 中的每个元素,分别向左和向右扫描并记录左边的最大高度,然后计算每个下标位置能接到的雨水量。

假设数组 height 的长度为 n n n,朴素的做法需要对每个下标位置使用 O ( n ) O(n) O(n) 的时间向两边扫描并获得最大高度,总的时间复杂度为 O ( n 2 ) O(n^2) O(n2)

本题数据规模达到 1 0 5 10^5 105,时间复杂度为 O ( n 2 ) O(n^2) O(n2) 的解法一定超时。

预处理

上述做法的时间复杂度较高是因为需要对每个下标位置都向两边扫描。如果已经知道每个位置两边的最大高度,则可以在 O(n)O(n)O(n) 的时间内得到能接的雨水总量。

于是可以提前计算并记录每个位置两侧的最大高度。

具体地,维护数组 leftMaxrightMax,其中 leftMax[i] 表示下标 i 及其左边的位置中,height 的最大高度。rightMax[i] 表示下标 i 及其右边的位置中,height 的最大高度。显然有初始化 leftMax[0] = height[0]rightMax[n-1] = height[n-1],其余位置的元素计算比较简单,直接看代码。

代码

class Solution {
public:int trap(vector<int>& height) {int n = height.size();if (n == 0) {return 0;}// 更新 leftMaxvector<int> leftMax(n);leftMax[0] = height[0];for (int i = 1; i < n; ++i) {leftMax[i] = max(leftMax[i-1], height[i]);}// 更新 rightMaxvector<int> rightMax(n);rightMax[n-1] = height[n-1];for (int i = n - 2; i >= 0; --i) {rightMax[i] = max(rightMax[i+1], height[i]);}int res = 0;for (int i = 0; i < n; ++i) {res += min(leftMax[i], rightMax[i]) - height[i];}return res;}
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n)

空间复杂度: O ( n ) O(n) O(n)

方法二:单调栈

思路

除了计算并存储每个位置两边的最大高度以外,也可以用单调栈计算能接的雨水总量。

维护一个单调栈,单调栈存储的是下标,满足从栈底到栈顶的下标对应的数组 height 中的元素递减。

从左到右遍历数组,遍历到下标 i 时,如果栈内至少有两个元素,记栈顶元素为 toptop 的下面一个元素是 left,则一定有 height[left]≥height[top]。如果 height[i]>height[top],则得到一个可以接雨水的区域,该区域的宽度是 i−left−1,高度是 min⁡(height[left],height[i])−height[top],根据宽度和高度即可计算得到该区域能接的雨水量。

为了得到 left,需要将 top 出栈。在对 top 计算能接的雨水量之后,left 变成新的 top,重复上述操作,直到栈变为空,或者栈顶下标对应的 height 中的元素大于或等于 height[i]

在对下标 i 处计算能接的雨水量之后,将 i 入栈,继续遍历后面的下标,计算能接的雨水量。遍历结束之后即可得到能接的雨水总量。

代码

class Solution {
public:int trap(vector<int>& height) {int n = height.size(), res = 0;stack<int> stk;for (int i = 0; i < n; ++i) {while (!stk.empty() && height[i] > height[stk.top()]) {int top = stk.top(); stk.pop();if (stk.empty()) {break;}int left = stk.top();int curWidth = i - left - 1;int curHeigh = min(height[left], height[i]) - height[top];res += curWidth * curHeigh;}stk.push(i);}return res;}
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n) n n n 是数组 height 的长度。从 0 到 n-1 的每个下标最多只会入栈和出栈一次。

空间复杂度: O ( n ) O(n) O(n)。空间复杂度主要取决于栈空间,栈的大小不会超过 n。

方法三:双指针

思路

下标 i 处能接的雨水量由 leftMax[i]rightMax[i] 中的最小值决定。数组 leftMax 是从左到右计算的,数组 rightMax 是从右到左计算的,我们可以使用双指针和两个变量来代替两个数组。

使用 height[left]height[right] 来更新 leftMaxrightMax 比较容易理解就不说了。重点说一下如何更新 res

  • height[left] < heigh[right] 时,必有 leftMax < rightMax,下标 left 处一定可以盛水;
  • height[left] >= heigh[right] 时,必有 leftMax >= rightMax,下标 right 处一定可以盛水。

代码

class Solution {
public:int trap(vector<int>& height) {int res = 0;int left = 0, right = height.size() - 1;int leftMax = 0, rightMax = 0;while (left < right) {leftMax = max(leftMax, height[left]);rightMax = max(rightMax, height[right]);if (height[left] < height[right]) {res += leftMax - height[left++]; }else {res += rightMax - height[right--];}}return res;}
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n) n n n 为数组 height 的长度。

空间复杂度: O ( 1 ) O(1) O(1)


写在最后

如果您发现文章有任何错误或者对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家有更优的时间、空间复杂度的方法,欢迎评论区交流。

最后,感谢您的阅读,如果有所收获的话可以给我点一个 👍 哦。

这篇关于【面试经典 150 | 数组】接雨水的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/951335

相关文章

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

Java 字符数组转字符串的常用方法

《Java字符数组转字符串的常用方法》文章总结了在Java中将字符数组转换为字符串的几种常用方法,包括使用String构造函数、String.valueOf()方法、StringBuilder以及A... 目录1. 使用String构造函数1.1 基本转换方法1.2 注意事项2. 使用String.valu

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2

vue如何监听对象或者数组某个属性的变化详解

《vue如何监听对象或者数组某个属性的变化详解》这篇文章主要给大家介绍了关于vue如何监听对象或者数组某个属性的变化,在Vue.js中可以通过watch监听属性变化并动态修改其他属性的值,watch通... 目录前言用watch监听深度监听使用计算属性watch和计算属性的区别在vue 3中使用watchE

字节面试 | 如何测试RocketMQ、RocketMQ?

字节面试:RocketMQ是怎么测试的呢? 答: 首先保证消息的消费正确、设计逆向用例,在验证消息内容为空等情况时的消费正确性; 推送大批量MQ,通过Admin控制台查看MQ消费的情况,是否出现消费假死、TPS是否正常等等问题。(上述都是临场发挥,但是RocketMQ真正的测试点,还真的需要探讨) 01 先了解RocketMQ 作为测试也是要简单了解RocketMQ。简单来说,就是一个分

hdu2241(二分+合并数组)

题意:判断是否存在a+b+c = x,a,b,c分别属于集合A,B,C 如果用暴力会超时,所以这里用到了数组合并,将b,c数组合并成d,d数组存的是b,c数组元素的和,然后对d数组进行二分就可以了 代码如下(附注释): #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<que

hdu 1166 敌兵布阵(树状数组 or 线段树)

题意是求一个线段的和,在线段上可以进行加减的修改。 树状数组的模板题。 代码: #include <stdio.h>#include <string.h>const int maxn = 50000 + 1;int c[maxn];int n;int lowbit(int x){return x & -x;}void add(int x, int num){while

秋招最新大模型算法面试,熬夜都要肝完它

💥大家在面试大模型LLM这个板块的时候,不知道面试完会不会复盘、总结,做笔记的习惯,这份大模型算法岗面试八股笔记也帮助不少人拿到过offer ✨对于面试大模型算法工程师会有一定的帮助,都附有完整答案,熬夜也要看完,祝大家一臂之力 这份《大模型算法工程师面试题》已经上传CSDN,还有完整版的大模型 AI 学习资料,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

C语言:柔性数组

数组定义 柔性数组 err int arr[0] = {0}; // ERROR 柔性数组 // 常见struct Test{int len;char arr[1024];} // 柔性数组struct Test{int len;char arr[0];}struct Test *t;t = malloc(sizeof(Test) + 11);strcpy(t->arr,