法一:找规律 发现数组中每个索引处的水的个数 是如何计算的
针对每根柱子上方可以存储的水,需要往前看、往后看,分别找出当前柱子前面最高的柱子和后面最高的柱子。
public int trap (int[] height) {
int sum = 0;
// 最两端的列不用考虑,因为一定不会有水。所以下标从1到length-2
for (int i=1;i<height.length-1;i++) {
int max_left = 0;
// 找出左边最高
for (int j=i-1;j>=0;j--) {
max_left = Math.max(max_left, height[j]);
}
int max_right = 0;
// 找出右边最高
for (int j=i+1;j<height.length;j++) {
max_right = Math.max(max_right, height[j]);
}
// 找出两端较小的
int min = Math.min(max_left, max_right);
// 只有较小的一端大于当前列的高度才会有水,其他情况不会有水
if (min > height[i]) {
sum += min - height[i];
}
}
return sum;
}
法二:双指针法
分别从数组的最前面和最后面开始,这两个指针是互不影响,都是各走各的,但是如何确定当前指针走过的地方能存的雨水数量呢?
这个时候,我们就需要两块挡板leftMax和rightMax,这两块挡板最开始都是挡在最外面的墙边,随着两个指针前进,leftMax代表的是left走过的路中最高的墙,rightMax同理。
那么如何计算雨水量呢?比较左右两个挡板的高度,然后根据两个挡板各自的指针配合计算:
- 如果左边挡板的高度小于右边的挡板高度,那么左边指针之前的雨水量取决于leftMax和height[left]的大小关系,如果前者大于后者,那么容量等与前者减去后者;反之,容量为0(可以参考解法一中的图来理解)
- 如果左边挡板的高度大于等于右边挡板的高度,与上一种情况基本相同,只不过是求的右边的雨水量。
- 在每次移动指针之后,我们要将挡板更新到最大值。
其实道理也是比较简单,用宏观的思维去看待整个问题,最起码先保证两边的墙的高度(两块挡板),然后依次去到其中各个墙之间能装多少雨水的问题上。(求每次更新最高的挡板和指针指向的墙之间可以存储的雨水量)
public int trap(int[] height) {
if (height.length == 0) return 0;
int left = 0; // 左指针
int right = height.length-1; // 右指针
int leftMax = 0; // 左挡板,指的是left指针走过的路中,最高的挡板
int rightMax = 0;
int result = 0;
while (left <= right) {
if (leftMax < rightMax) { // 左挡板低,就根据左挡板判断是否蓄水了,更新左挡板为高的,并移动指针
result += leftMax - height[left] > 0 ?
leftMax - height[left] : 0;
leftMax = Math.max(leftMax, height[left]);
left++;
} else { // 右挡板低,就根据右挡板判断是否蓄水了,更新右挡板为高的,并移动指针
result += rightMax - height[right] > 0 ?
rightMax - height[right] : 0;
rightMax = Math.max(rightMax, height[right]);
right--;
}
}
return result;
}
法三:栈 (来自作者windliang)
用栈保存每个墙。当遍历墙的高度的时候,如果当前的高度小于栈顶的墙的高度,则会有积水,就将当前墙的告诉进栈。
如果当前高度大于栈顶的墙的高度,说明之前的积水到这里停下,我们可以计算下有多少积水了。计算完,就把当前的墙继续入栈,作为新的积水的墙。
总体的原则就是,当前高度小于等于栈顶高度,入栈,指针后移。当前高度大于栈顶高度,出栈,计算出当前墙和栈顶的墙之间水的多少,然后计算当前的高度和新栈的高度的关系,重复第 2 步。直到当前墙的高度不大于栈顶高度或者栈空,然后把当前墙入栈,指针后移。
(待更新)