力扣42. 接雨水

题目来源(力扣):

https://leetcode.cn/problems/trapping-rain-water/description/

题目描述:

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

基本思路1

最直观的方法,对于第k根柱子,扫描它左侧的最高柱子的高度lmax,和右侧的最高柱子的高度rmax,
然后取lmax、rmax中的较小值min(lmax,rmax),再用height[h]-min(lmax,rmax),就能得到当前柱子能储蓄的水量,(如果大于0就)累加进答案ans
代码如下

代码实现1

class Solution
{
public:
	int trap(vector<int> &height)
	{
		int lmax, rmax, minl_r;
		int ans = 0;
		for (int k = 0; k < height.size(); k++)
		{
			lmax = height[k];
			rmax = height[k];
			for (int i = 0; i < k; i++) // 左侧最高的柱子
				if (height[i] > lmax)
					lmax = height[i];
			for (int i = k; i < height.size(); i++) // 右侧最高的柱子
				if (height[i] > rmax)
					rmax = height[i];
			minl_r = min(lmax, rmax); // 取2根柱子中较低的那一根
			if (minl_r > height[k])
				ans += minl_r - height[k];
		}
		return ans;
	}
};

时间复杂度O(n^2)
时间主要花费在查找lmax和rmax上

时间复杂度

O(n^2)

基本思路2

基本上和思路1一致,注意到在查找lmax和rmax的时候,我们使用的方式是简单的遍历,
如果能够通过预处理,在O(1)时间内求出第k根柱子的左、右侧最高的柱子高度。
预处理实际上是一个简单的动态规划,即lmax[i]=max(height[i],lmax[i-1]);
只需要预先得到lmax[]即可(rmax[]同理)即可
代码如下

代码实现2

class Solution
{
public:
	int trap(vector<int> &height)
	{
		int len = height.size();
		vector<int> lmax(len, 0), rmax(len, 0);
		int minl_r, ans = 0;
		lmax[0] = height[0];
		for (int i = 1; i < len; i++)
			lmax[i] = max(height[i], lmax[i - 1]);
		rmax[len - 1] = height[len - 1];
		for (int i = len - 2; i >= 0; i--)
			rmax[i] = max(height[i], rmax[i + 1]);
		for (int k = 0; k < len; k++)
		{
			minl_r = min(lmax[k], rmax[k]); // 取2根柱子中较低的那一根
			if (minl_r > height[k])
				ans += minl_r - height[k];
		}
		return ans;
	}
};

时间复杂度O(n)
其中预处理时间复杂度O(2*n),得到答案时间复杂度O(n)
所以总时间复杂度O(n)

基本思路3

思路1(思路2)是按列求得每一列的雨水量,最后累加得到ans。
实际上也可以按行求得雨水面积,看起来就像是求图中一个个小坑积累的雨水面积,这就要使用另外一种方式来求解,
利用到的算法容器为单调栈

需要思考柱子什么时候可以形成小坑
当柱子单调递减(或单调递增)时,不会形成小坑,不会储水,例如:
[9,7,3]

接下来,遇见一根较高的柱子,就会形成小坑
[9,7,3,6]

由此可见,形成小坑的数据是先减后增的。

而之所以按行求解雨水量,是因为遍历到第k跟柱子时,我们不知道k+1以及之后的柱子的情况,可能使得当前的小坑储水量变大或者形成新的小坑
因此只需要先管好已经形成的小坑的水量就好了,按行求解会十分方便也符合逻辑。

单调栈的作用就是用于记录高柱子的位置,即小坑的边界,便于在发现小坑时求解储水量。

代码如下

代码实现3

class Solution
{
public:
	int trap(vector<int> &height)
	{
		stack<int> sta; // 记录下标
		int ans = 0;
		sta.push(0);
		for (int i = 1; i < height.size(); i++)
		{
			if (height[sta.top()] > height[i])
				sta.push(i);
			else if (height[sta.top()] == height[i])
			{
				sta.pop();
				sta.push(i);
			}
			else
			{ //(可能)形成了小坑
				int l, r = i, wei;
				// 计算雨水累计值
				while (sta.size() > 1 && height[sta.top()] < height[i])
				{ // 只有栈中元素数量大于2时才形成小坑
					wei = sta.top();
					sta.pop();
					l = sta.top();
					ans += (r - l - 1) * (min(height[l], height[r]) - height[wei]);
				}
				// 维护单调栈
				while (sta.size() && height[i] >= height[sta.top()])
					sta.pop();
				sta.push(i);
			}
		}
		return ans;
	}
};

时间复杂度O(n)
注意每个元素至多入栈和出栈一次
与单调队列的题目比较类似 https://www.cnblogs.com/hb-computer/articles/18519501

posted @ 2024-11-08 10:10  HB_Computer  阅读(51)  评论(0)    收藏  举报