题目描述
这是leetcode上的题。
原题链接
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.
Example:
Input: [0,1,0,2,1,0,1,3,2,1,2,1]
Output: 6
问题分析
对于接雨水问题,想要能接住雨水,则要求两边的柱子高于中间柱子。而接住的雨水量则由两边柱子中最低的那一根以及中间柱子的高度来决定。在这样的思考下,我给出了三种不同的解法,三种解法各有优缺点。
解法1
算法分析
我最先想到的解法是顺着题目所给的数组依次计算出每一个柱子能够接住的雨水。
这算法的优点是代码简单易懂。缺点是由于有双层循环嵌套,时间复杂度高。
粗略估算时间复杂度的为O(n^2),空间复杂度为O(1)。
代码实现
public static int trap(int[] height) {
if (height.length < 3) {
return 0;
} else {
int filledWater = 0;
int leftWall;// 左壁
int rightWall = height[height.length - 1];// 右壁
int canWater;
int k = 0;
int i,j;
for (i = height.length - 2; i > 0; i--) {
for (j = 0; j < i; j++) {
if (height[j] > height[k]) {
k = j;
}
}
leftWall = height[k];
rightWall = Math.max(rightWall, height[i + 1]);
canWater = Math.min(leftWall, rightWall) - height[i];
filledWater += canWater > 0 ? canWater : 0;
}
return filledWater;
}
}
解法2
算法分析
在解法1的基础上,我为了克服时间复杂度高的问题,我设置了两个数组,分别用来存储柱子的两边最高柱子高度。以此改进算法得到解法2。对于这个算法,其优点为耗时短,但是空间开销巨大。
粗略估算,其时间复杂度为O(n),空间复杂度为O(n)。
代码实现
public static int trap(int[] height) {
if (height.length < 3) {
return 0;
} else {
int filledWater = 0;
int[] leftWall = new int[height.length - 2];// 左壁
int[] rightWall = new int[height.length - 2];// 右壁
int canWater;
leftWall[0] = height[0];
for (int i = 1; i < leftWall.length; i++) {
leftWall[i] = Math.max(height[i], leftWall[i - 1]);
}
rightWall[rightWall.length - 1] = height[height.length - 1];
for (int i = rightWall.length - 2; i >= 0; i--) {
rightWall[i] = Math.max(rightWall[i + 1], height[i + 2]);
}
for (int i = 0; i < rightWall.length; i++) {
canWater = Math.min(rightWall[i], leftWall[i]) - height[i + 1];
filledWater += canWater > 0 ? canWater : 0;
}
return filledWater;
}
}
解法3
算法分析
由于算法2空间复杂度过高,我便思考其改良方案。我观察存储柱子两侧最高柱子的数组时,发现,以存在柱子左侧最高柱子的数组为例,在全柱子中最高柱子的右侧的柱子,其左侧最高柱子都是该全柱子最高柱子。因此,我可以把题目所给的柱子数组以最高柱子为界点拆分为两个数组,进而分别求得能接住的雨水数量。
分析到了这一步,我们很容易会发现,这与递归的思想十分相像。因此,我们可以尝试采用递归来解决问题。
首先,设计边界条件。边界条件有两个。一个是该柱子能否储水,也就是该柱子的左右最高柱子十分紧挨在一起。还有一个条件是该数组能否再拆分为两个数组,也就是该数组中最高的柱子十分位于数组的两端。若不位于数组的两端,我们可以直接去求得该数组能够存储的水量。
初略估算,该算法的时间复杂度为O(n*log n),空间复杂度为O(log n)。
代码实现
public static int trapBegin(int[] height) {
return trap(height, 0, height.length - 1);
}
public static int trap(int[] height, int leftWall, int rightWall) {
if (rightWall - leftWall < 2) {
return 0;
} else {
int k = leftWall + 1;
for (int i = k + 1; i < rightWall; i++) {
k = height[k] > height[i] ? k : i;
}
int m = Math.min(height[leftWall], height[rightWall]);
int ans = 0;
if (height[k] < m) {
for (int i = leftWall + 1; i < rightWall; i++) {
ans += m - height[i];
}
return ans;
} else {
int a = trap(height, leftWall, k);
int b = trap(height, k, rightWall);
return a + b;
}
}
}
解法四
算法简介
递归算法的时间复杂的居然高于解法2,这不可以忍。我冥思苦想了一天,突然灵光一闪,有了一点思路。该算法已经有了设想。但是由于暂时没有时间,就暂且放一边。具体的代码实现等我有时间验证了该算法的可行性时我再补。