leetcode题解:第42题Trapping Rain Water

题目

Trapping Rain Water

Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.

The above elevation map is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped.

Example:

Input: [0,1,0,2,1,0,1,3,2,1,2,1]
Output: 6

分析

这道题目的意思是给出一个数组来代表柱子的高度,让我们计算可以有多少水陷在这些柱子围成的空隙中,注意最左边和最右边是没有阻挡作用的。

解法一

要让水陷入,就必须有左右各一个柱子比中间的所有柱子都高,因此我的想法是用一个bottom变量来记录矮一点的左边柱子,用一个栈stack来存放两个柱子中的矮柱子高度,当遍历到高度>=bottom的柱子时就形成了一个可以陷入水的空隙,再遍历数组去找到所有可能的这种空隙。算法思路如下:

  1. 数组arr长度<=2时无法形成空隙,必为0

  2. bottom = arr[0]stack<int> s

for i : arr
	if (bottom <= arr[i])
        //计算空隙中的水量
    else s.push(arr[i])
  1. 而计算空隙中的水量也很简单,其实就是个计算面积的问题:以矮柱子bottom的高度为宽,中间柱子数bar_num为宽计算整个长方形面积,再减去中间柱子的总高度rock即可:water = bottom * bar_num - rock

  2. 要得到中间的柱子数bar_num和中间柱子的总高度rock,只需一步步弹出栈即可

循环部分的代码如下:

for (int i = 1; i < height.size(); ++i) {
	if (bottom <= height[i]) {
	//计算水量
	int rock = 0, bar_num = 0;
	while (!s.empty()) {
		int tmp = s.top();
        s.pop();
        rock += tmp;
        bar_num++;
    }
    int water = bottom * bar_num - rock;
    result += water;
    bottom = height[i];
    top = i;
}
	else s.push(height[i]);
}

但这个思路还没有完全解决问题,因为我假设的bottom是左边的柱子,然后通过找高度>=bottom的柱子来寻找空隙,但是如果没有高度>=bottom的柱子也是可能存在空隙的,比如例子中最右边的那个空隙。解决办法是记录最高的那个柱子,然后再从右边以同样的方法遍历,直到最高的这个柱子为止,来寻找那些第一次遍历时错过的空隙(十分暴力的解法)。

代码
#include<iostream>
#include<stack>
#include<vector>
using namespace std;

class Solution {
public:
    int trap(vector<int>& height) {
        if (height.size() <= 2) return 0;
        int result = 0;
        stack<int> s;
        int bottom = height[0];
        int top = 0;
        for (int i = 1; i < height.size(); ++i) {
            if (bottom <= height[i]) {
                //计算水量
                int rock = 0, bar_num = 0;
                while (!s.empty()) {
                    int tmp = s.top();
                    s.pop();
                    rock += tmp;
                    bar_num++;
                }
                int water = bottom * bar_num - rock;
                result += water;
                bottom = height[i];
                top = i;
            }
            else s.push(height[i]);
        }
        stack<int> s2;
        bottom = height[height.size() - 1];
        for (int i = height.size() - 2; i >= top; --i) {
            if (bottom <= height[i]) {
                //计算水量
                int rock = 0, bar_num = 0;
                while (!s2.empty()) {
                    int tmp = s2.top();
                    s2.pop();
                    rock += tmp;
                    bar_num++;
                }
                int water = bottom * bar_num - rock;
                result += water;
                bottom = height[i];
            }
            else s2.push(height[i]);
        }
        return result;
    }
    
};

这种解法的时间复杂度是O(n2),而且最坏情况(最高的柱子为最左边的柱子)下需要遍历两次,效率很低了,必须得优化下。

解法二

我的优化思路是从堆栈入手,一开始我使用栈是因为想用它来管理那些中间的矮柱子,但其实好像并不需要,因为我只需要得到3条关于它们的信息:

  1. 高度<=bottom
  2. 矮柱子个数
  3. 矮柱子总高度

这3条信息在遍历的过程中都可以得到,而额外弹出栈还需要再套一个循环,完全没必要,因此使用堆栈这一点是显而易见需要优化的。

代码
#include<iostream>
#include<stack>
#include<vector>
using namespace std;

class Solution {
public:
    int trap(vector<int>& height) {
        if (height.size() <= 2) return 0;
        int result = 0;
        int bottom = height[0];
        int top = 0;
		int rock = 0, bar_num = 0;
        for (int i = 1; i < height.size(); ++i) {
            if (bottom <= height[i]) {
                //计算水量
                int water = bottom * bar_num - rock;
                result += water;
                bottom = height[i];
                top = i;
				rock = bar_num = 0;
            }
            else {
				rock += height[i];
				bar_num++;
			}
        }
        bottom = height[height.size() - 1];
		rock = bar_num = 0;
        for (int i = height.size() - 2; i >= top; --i) {
            if (bottom <= height[i]) {
                //计算水量
                int water = bottom * bar_num - rock;
                result += water;
                bottom = height[i];
				rock = bar_num = 0;
            }
            else {
				rock += height[i];
				bar_num++;
			}
        }
        return result;
    }
    
};

int main() {
    vector<int> test = {0,1,0,2,1,0,1,3,2,1,2,1};
    Solution s;
    cout << s.trap(test);
}

优化后的代码时间复杂度为O(n),但是最坏情况下还是可能遍历两次,因此我还需要找出一次遍历就能解决问题的算法,这可能要同时从两边开始遍历。

解法三

先找出最高点,再从左右两边开始向最高点遍历。这种解法在时间复杂度为O(n),因为要找最高点,所以会多遍历一遍,但计算结果则只需要遍历整个数组一遍。

代码
class Solution {
public:
    int trap(vector<int>& height) {
        if (height.size() <= 2) return 0;
        int result = 0;
        int top = 0;
        for (int i = 0; i < height.size(); ++i) {
            if (height[i] > height[top]) top = i;
        }
        int bottom = 0;
        int rock = 0, num = 0;
        for (int i = 1; i <= top; ++i) {
            if (height[i] >= height[bottom]) {
                result += height[bottom] * num - rock;
                bottom = i;
                rock = num = 0;
            }
            else {
                rock += height[i];
                num++;
            }
        }
        bottom = height.size() - 1;
        for (int i = height.size() -2; i >= top; --i) {
            if (height[i] >= height[bottom]) {
                result += height[bottom] * num - rock;
                bottom = i;
                rock = num = 0;
            }
            else {
                rock += height[i];
                num++;
            }
        }
        return result;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值