LeetCode刷题05-42. 接雨水 tag 数组 hard 407. 接雨水 II hard
四种解法
一、左右指针对撞
思路:左右比较取小值,将lower与记录的水位线进行比较,如果水位线高于lower,则水位线设为level=lower,然后记录该处可存水量water+=level-lower。这里相当于,时刻记录左右两边的最小值作为水位线,然后用水位线减去本处高度作为存水量。
class Solution { public: int trap(vector<int>& height) { int n=height.size(); int l=0,r=n-1,lower=0,level=0; int water=0; while(l<r) { lower=min(height[l],height[r]); if(height[l]<height[r]) l++; else r--; level=max(lower,level); water+=level-lower; } return water; } };
二、按照列求
思路
对每一列来说,都判断自己这一列能不能装水,如何判断呢?
看自己左边最高的墙(不包括自己)和右边最高的墙(不包括自己),根据短板效应取这俩中更小的那个值lowWall
lowWall比自己高那才可能出现凹坑才能存水对吧,旁边的比自己矮那不流出去了
然后计算这一列上存了多少水
首先两端的墙不用考虑,存不了,直接从第二个看
比如下面红色这一列能存几个水呢?
1.红色自己高度是1,左边最高是绿色,高度为0,右边最高是紫色,值为3,根据短板效应取lowWall = 0,
2.lowWall没有自己高,装不了
来看个能装的例子,下图我们来看红色框里这一列,它能装多少水呢?
首先,左边最高的墙是绿色,高度为2,右边最高的墙为紫色,高度为3,lowWall取值为2
lowWall大于红色自身高度1所以能装啊
那每列能装多少水呢?根据上图看出来,是(lowWall-自身高度)
最后按照这个方法计算每一列就完事了
逐句注释的代码实现
public int trap(int[] height) { int sum = 0; //最两端的列不用考虑,因为一定不会有水。所以下标从 1 到 length - 2 for (int i = 1; i < height.length - 1; i++) { int max_left = 0; //找出左边最高 for (int j = i - 1; j >= 0; j--) { if (height[j] > max_left) { max_left = height[j]; } } int max_right = 0; //找出右边最高 for (int j = i + 1; j < height.length; j++) { if (height[j] > max_right) { max_right = height[j]; } } //找出两端较小的 int min = Math.min(max_left, max_right); //只有较小的一段大于当前列的高度才会有水,其他情况不会有水 if (min > height[i]) { sum = sum + (min - height[i]); } } return sum; }
时间复杂度:O(n²),遍历每一列需要 nn,找出左边最高和右边最高的墙加起来刚好又是一个 n,所以是 n²。
空间复杂度:O(1)
三、动态规划(优化刚才的按列)
思路
刚才代码中出现了一些重复出现的循环遍历,每次计算新的一列时候,我们都要重新找两边最高的墙,完全没必要啊,利用动态规划的思想,把这些信息放到一个数组里就ok,动态规划的思想很复杂,在这里说不清,以后再详细讲解,反正大概就是把重复的方法归纳起来复用
逐句注释的代码实现
public int trap(int[] height) { //sum计算总和 // 新建两个长度和height一样的数组用来存 每一列的左右最高墙在height中的索引 int sum = 0; int[] max_left = new int[height.length]; int[] max_right = new int[height.length]; //求所有列的左边最高墙 索引都放在max_left里 for (int i = 1; i < height.length - 1; i++) { max_left[i] = Math.max(max_left[i - 1], height[i - 1]); } //求所有列的右边最高墙 索引都放在max_right里 for (int i = height.length - 2; i >= 0; i--) { max_right[i] = Math.max(max_right[i + 1], height[i + 1]); } //计算每一列的能存多少水累加起来 这一步和上面方法没区别 for (int i = 1; i < height.length - 1; i++) { int min = Math.min(max_left[i], max_right[i]); if (min > height[i]) { sum = sum + (min - height[i]); } } return sum; }
动态规划优化了求一侧最大值的操作,其余代码没变化
时间复杂度:O(n)
空间复杂度:O(n),用来保存每一列左边最高的墙和右边最高的墙。
相比刚才的方法,时间复杂度降了下来,空间复杂度略有上升
四、双指针(把刚才上升的空间复杂度给降下来成为指标最优的解法)
思路
这道题中,可以看到,max_left[ i ] 和 max_right[ i ] 数组中的元素我们其实只用一次,然后就再也不会用到了。所以我们可以不用数组,只用一个元素就行了。
逐句注释的代码实现
我们先改造下 max_left
public int trap(int[] height) { int sum = 0; int max_left = 0; //右侧的没改还用数组存 int[] max_right = new int[height.length]; for (int i = height.length - 2; i >= 0; i--) { max_right[i] = Math.max(max_right[i + 1], height[i + 1]); } //左侧的改了 for (int i = 1; i < height.length - 1; i++) { max_left = Math.max(max_left, height[i - 1]); int min = Math.min(max_left, max_right[i]); if (min > height[i]) { sum = sum + (min - height[i]); } } return sum; }
时间复杂度: O(n)
空间复杂度:O(1)
五、栈
思路
这道题好像就是想考栈= =
就是把墙的高度压入栈中,栈顶元素就是上一个墙的高度,如果指针指的当前墙比上一个墙高,那就说明,能存水,没有上一个墙高,那就存不了水,把这个墙放进去
具体思路都写代码注释里面了,注释已经不能再细了
逐句注释的代码实现
public int trap(int[] height) { /** * 初始栈 * 初始总和 * 初始指针指向第一个元素 */ Stack<Integer> stack = new Stack<>(); int sum = 0; int currIndex = 0; /** * 最后用大循环包围 */ while (currIndex < height.length) { /** * 如果下一个比栈顶的大,那就进行一系列操作 */ while (!stack.empty() && height[currIndex] > height[stack.peek()]) { /** * 1.首先取出栈顶元素 是计算面积的底 * 2.然后刚才的底没用了,出栈,我们要他之前的那个墙(新的栈顶)的高度,和currIndex指针指向的墙比,哪个矮,哪个-底就等于计算面积的高 * (矮墙-底) X 底边宽 = 面积 * 3.底边宽怎么求? 底边宽 = 后面的墙-前面的墙-1 * 4.所以最终 sum = 面积 = (矮墙-底) X 底边宽 */ //1. int bottomHeight = height[stack.peek()]; //2. stack.pop(); //这里注意 要是出栈以后 栈空了那就跳出循环进行下一个height里的元素操作 if (stack.empty()) break; //go on int lowerWall = Math.min(height[currIndex], height[stack.peek()]); //3. int bottomWide = currIndex - stack.peek() - 1; //4. sum = sum + (lowerWall - bottomHeight) * bottomWide; } /** * 如果下一个比栈顶的小,那就直接入栈 */ stack.push(currIndex); /** * 遍历记得指针往后走 */ currIndex++; } return sum; } }
单调栈,c++代码:
栈内从栈底到栈顶单调递减。所以当height[i]<=height[s.top()]时,直接压栈。当height[i]>height[s.top()]时,可以蓄水。这时,先出栈k=s.top(),s.pop(),然后计算以bottom=height[k]为底的凹槽蓄水量
class Solution { public: int trap(vector<int>& height) { int n=height.size(); int sum=0; stack<int> s; int i=0; while(i<n){ while(!s.empty()&&height[i]>height[s.top()]){ int h=height[s.top()]; s.pop(); if(s.empty()) break; int dis = i-s.top()-1; int minh = min(height[i], height[s.top()]); sum+=dis*(minh - h); } s.push(i); i++; } return sum; } };
————————————————
版权声明:本文为CSDN博主「RookieZc」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41132565/article/details/109160614
407. 接雨水 II
题目:
给你一个 m x n 的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。
示例 1:
输入: heightMap = [[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]]
输出: 4
解释: 下雨后,雨水将会被上图蓝色的方块中。总的接雨水量为1+2+1=4。
思路:
这里不能单纯按照上下左右的最大值来求解。
这是因为储水区域是联通的,例如
12 13 1 12
13 4 13 12
13 8 10 12
12 13 12 12
针对该矩阵,结果为14而不是15.这是因为对于4来说,因为12-10-8-4的联通水域,使得4处的储水高度为12-4.
所以求解该题,应该使用联通区域深度遍历的方式。
用小根堆记录四边,然后每次取出最小的点求联通的区域,并记录访问过的联通区域。
class Solution { public: struct State{ State(int h,int x,int y){ this->h=h; this->x=x; this->y=y; } int h; int x; int y; // 小根堆 bool operator<(const State& s) const{ return h>s.h; } }; int trapRainWater(vector<vector<int>>& heightMap) { priority_queue<State> pq; int m=heightMap.size(); int n=heightMap[0].size(); // 记录访问过的点 vector<vector<bool>> visited(m,vector<bool>(n,false)); // 将左右两边放入堆 for(int i=0;i<m;++i){ pq.push(State(heightMap[i][0],i,0)); pq.push(State(heightMap[i][n-1],i,n-1)); visited[i][0]=true; visited[i][n-1]=true; } // 将上下两边放入堆 for(int j=0;j<n;++j){ pq.push(State(heightMap[0][j],0,j)); pq.push(State(heightMap[m-1][j],m-1,j)); visited[0][j]=true; visited[m-1][j]=true; } int ret=0; int dist[][2]={{0,1},{0,-1},{1,0},{-1,0}}; while(!pq.empty()){ State s=pq.top(); pq.pop(); int h=s.h; int nx=s.x; int ny=s.y; for(int i=0;i<4;++i){ // 求四周的联通点 int x=nx+dist[i][0]; int y=ny+dist[i][1]; // 如果合法且未被访问过 if(x>=0&&x<m&&y>=0&&y<n&&!visited[x][y]){ // 如果可以储水,记录 if(h>heightMap[x][y]) ret+=h-heightMap[x][y]; visited[x][y]=true; // 这里放入时记录最大高度 pq.push(State(max(h,heightMap[x][y]),x,y)); } } } return ret; } };