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; } };