(暴力+剪枝)leetcode困难42. 接雨水

题目

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

样例

示例 1:

在这里插入图片描述

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。

示例 2:
输入:height = [4,2,0,3,2,5]
输出:9

提示:
n == height.length
1 <= n <= 2 * 104
0 <= height[i] <= 105

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/trapping-rain-water
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

分析

根据图片很容易读明白题意,首先我们思考,在一个格子上如何判断它可以最多接的雨水的量,比如2 0 2 中间的格子就可以接2,2 0 100中间的格子也可以接2,可以说是木桶效应,3 2 0 100,0的格子可以接3,所以一个格子能接多少水取决于它左边和右边的柱子,我们要分别找出左边最高的柱子和右边最高的柱子,然后在这两个柱子中找出较小的那个再减去我们当前格子上的柱子的高度,就是最多能接的水。
之前听了九章算法的简单老师讲bfs,他说如果一开始没有想到更好的解决办法,暴力就是最好的办法,反正暴力的办法写出来也不会耽误多少时间,我们就直接用暴力的方法然后分析如何优化(剪枝或记忆化搜索等等)

每次找出左边最大和右边最大,然后再取其中较小的那个,减去当前格子的高度(较小的要大于当前格子的高度,否则当前格子不能接水)

分析如何优化,我们外循环是每个格子,为n,内循环每次也是遍历整个数组,所以总的时间复杂度为O(n^2),这时考虑重复的步骤是什么,外循环没办法简化,因为每个格子是必须遍历到的,内循环每次找左边最大和右边最大是可以简化的。

简化左边最大,我们从左往右遍历,每次的格子都是下一个格子的左边,所以我们只需要比较当前格子和左边最大值的大小,如果当前格子要大,那我们下一个格子左边的最大格子就是当前格子

简化右边最大,从图中我们可以看出来,一直到格子的高度为3的那个格子我们才需要更新右边最大的格子,所以这里我们先循环一次,找出当前的右边最大,下次更新的时间是我们到了设定的那个最大格子才更新

代码部分

初始化

首先初始化一个数组,用来存放每个格子最多可以存放的雨水量,然后初始化代表左边和右边最大柱子高度的变量,初始化左边最高为第一个元素的高度,之后每次循环都会比较,如果当前柱子高度大于它,就会为下一个格子更新它,初始化右边最大为-1,因为不可能有柱子的高度为-1,特殊情况,第一次的时候我们要循环一次找出右边的最大值,之后当我们到达设定的最大值的那个柱子我们才更新它。

		int len=height.size();
		vector<int> f(len,0);
		int lm=height[0],rm=-1;
核心部分

更新右边最大

			int j=i+1;
		
			if(rm<0||height[i]==rm)		//如果当前到达最大值,更新 
			{
    
    
				rm=0;
				while(j<len)
				{
    
    
					if(height[j]>rm)
						rm=height[j];
					++j;
				}
			}

判断当前格子能接的雨水的量,当前的柱子要比左边和右边最大的柱子小才能接住雨水

			if(lm>height[i]&&rm>height[i])
			{
    
    
				f[i]=min(lm,rm)-height[i];
			}

为下一个柱子,更新左边最大

			
			if(height[i]>lm)		//当前值比左边最大值要大,更新下一个的lm 
				lm=height[i];

完整代码

class Solution {
    
    
public:
    int trap(vector<int>& height) {
    
    
		int len=height.size();
		vector<int> f(len,0);
		int lm=height[0],rm=-1;		
		
		for(int i=1;i<len-1;i++)
		{
    
    
			int j=i+1;
		
			if(rm<0||height[i]==rm)		//如果当前到达最大值,更新 
			{
    
    
				rm=0;
				while(j<len)
				{
    
    
					if(height[j]>rm)
						rm=height[j];
					++j;
				}
			}
			
			if(lm>height[i]&&rm>height[i])
			{
    
    
				f[i]=min(lm,rm)-height[i];
			}
			
			if(height[i]>lm)		//当前值比左边最大值要大,更新下一个的lm 
				lm=height[i];
			
		}
		
		long long sum=0;
		for(int i=1;i<len-1;i++)
			sum+=f[i];
			
		return sum;
    }
};

总结

看完一道题,如果一开始只能想到暴力的方法,就去写好了,写完判断重复的情况,然后进行剪枝,记忆化搜索等等
这道题目前我看过最完美的方法是利用动态规划,开辟两个状态数组,分别代表每个格子上左边最大和右边最大,然后几乎利用了和我的剪枝一样的思路,进行每一个格子判断。时间复杂度目测是O(n),代码的量要比我写的少很多

猜你喜欢

转载自blog.csdn.net/weixin_46035615/article/details/123818481