[LeetCode] 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.

For example, 
Given [0,1,0,2,1,0,1,3,2,1,2,1], return 6.

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. Thanks Marcos for contributing this image!

 

 这道收集雨水的题跟之前的那道 Largest Rectangle in Histogram 直方图中最大的矩形 有些类似,但是又不太一样,我最先想到的方法有些复杂,但是也能通过OJ,想法是遍历数组,找到局部最小值,方法是如果当前值大于或等于前一个值,或者当前值大于后一个值则跳过,找到了局部最小值后,然后我们首先向左找到左边的最大值,再找右边的最大值,找右边最大值时要注意当其大于左边最大时时就停止寻找了,然后算出从左边最大值到右边最大值之间能装的水量,之后从右边最大值的位置开始继续找局部最小值,以此类推直到遍历完整个数组,代码如下:

 解法一:

class Solution {

public:

    int trap(int A[], int n) {

        int res = 0, left = 0, right = 0, level = 0;

        for (int i = 1; i < n - 1; ++i) {

            if (A[i] >= A[i - 1] || A[i] > A[i + 1]) continue;

            for (left = i - 1; left > 0; --left) {

                if (A[left] >= A[left - 1]) break;

            }

            right = i + 1;

            for (int j = i + 1; j < n; ++j) {

                if (A[j] >= A[right]) {

                    right = j;

                    if (A[right] >= A[left]) break;

                }

            }

            level = min(A[left], A[right]);

            for (int j = left + 1; j < right; ++j) {

                if (level - A[j] > 0) res += (level - A[j]);

            }

            i = right;

        }

        return res;

    }

};

 

那么我们再来看另一种方法,这种方法是基于动态规划Dynamic Programming的,我们维护一个一维的dp数组,这个DP算法需要遍历两遍数组,第一遍遍历dp[i]中存入i位置左边的最大值,然后开始第二遍遍历数组,第二次遍历时找右边最大值,然后和左边最大值比较取其中的较小值,然后跟当前值A[i]相比,如果大于当前值,则将差值存入结果,代码如下:

解法二

// DP

class Solution {

public:

    int trap(int A[], int n) {

        int res = 0, mx = 0;

        vector<int> dp(n, 0);

        for (int i = 0; i < n; ++i) {

            dp[i] = mx;

            mx = max(mx, A[i]);

        }

        mx = 0;

        for (int i = n - 1; i >= 0; --i) {

            dp[i] = min(dp[i], mx);

            mx = max(mx, A[i]);

            if (dp[i] - A[i] > 0) res += (dp[i] - A[i]);

        }

        return res;

    }

};

 

最后我们来看一种只需要遍历一次即可的解法,这个算法需要left和right两个指针分别指向数组的首尾位置,从两边向中间扫描,在当前两指针确定的范围内,先比较两头找出较小值,如果较小值是left指向的值,则从左向右扫描,如果较小值是right指向的值,则从右向左扫描,若遇到的值比当较小值小,则将差值存入结果,如遇到的值大,则重新确定新的窗口范围,以此类推直至left和right指针重合,具体参见代码如下:

解法三

// One Pass

class Solution {

public:

    int trap(int A[], int n) {

        int res = 0, left = 0, right = n - 1;

        while (left < right) {

            int mn = min(A[left], A[right]);

            if (A[left] == mn) {

                ++left;

                while (left < right && A[left] < mn) {

                    res += (mn - A[left]);

                    ++left;

                }

            } else {

                --right;

                while (left < right && A[right] < mn) {

                    res += (mn - A[right]);

                    --right;

                }

            }

        }

        return res;

    }

};

 

你可能感兴趣的:(LeetCode)