题目
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
解法一:暴力解法
基本思路
要能接到雨水,那么需要两侧的柱子比自己高才行,而且还取决于两个柱子较低的那个。较低的柱子与当前柱子的高度差就是该柱子可以接到的雨水。
也就是问题转换成:求每个柱子左侧的最高柱子和右侧最高的柱子。
注意:有可能自己就是最高的柱子。这样接到的雨水就是0。
代码实现
public int trap(int[] height) {
int res = 0;
for (int i = 0; i < height.length; i++) {
int leftMax = 0, rightMax = 0;
for (int j = i; j >= 0; j--) {
leftMax = Math.max(leftMax, height[j]);
}
for (int j = i; j < height.length; j++) {
rightMax = Math.max(rightMax, height[j]);
}
res += Math.min(leftMax, rightMax) - height[i];
}
return res;
}
复杂度
时间复杂度:O(n^2)
空间复杂度:O(1)
解法二
基本思路
上面的想法在寻找左右两侧的最大值时,使用了两层循环,效率比较低。考虑在一层循环中解决。
以寻找左侧的最大值为例:由于遍历是从0号开始的,假设用leftMax[i]表示第i处左侧的最大值,那么leftMax[i-1]已经是第i-1处左侧的最大值了,那么第i处的最大值可以表示为Math.max(leftMax[i-1], height[i])。
在寻找右侧的最大值时想法一样,只不过要倒序遍历。
代码
public int trap(int[] height) {
if (height.length == 0) {
return 0;
}
int[] leftMax = new int[height.length];
int[] rightMax = new int[height.length];
leftMax[0] = height[0];
for (int i = 1; i < height.length; i++) {
leftMax[i] = Math.max(leftMax[i-1], height[i]);
}
rightMax[height.length-1] = height[height.length-1];
for (int i = height.length - 2; i >= 0; i--) {
rightMax[i] = Math.max(rightMax[i+1], height[i]);
}
int res = 0;
for (int i = 0; i < height.length - 1; i++) {
res += Math.min(leftMax[i], rightMax[i]) - height[i];
}
return res;
}
复杂度
时间复杂度:O(n)
空间复杂度:O(n)
解法三
基本思路
上面的解法空间复杂度太高了。在解题过程中,总是选择的是左右两侧较小的。使用左右双指针的方式来解决。
代码
public int trap(int[] height) {
if (height.length == 0) {
return 0;
}
int res = 0;
int left = 0, right = height.length - 1;
int leftMax = height[0], rightMax = height[height.length - 1];
while (left <= right) {
// 下面总是选择两侧较小的那个
if (leftMax <= rightMax) {
// 左侧的最大值小
// 计算新的leftMax,left递增
leftMax = Math.max(leftMax, height[left]);
res += leftMax - height[left];
left++;
} else {
// 右侧的最大值小
// 计算新的rightMax,right递减
rightMax = Math.max(rightMax, height[right]);
res += rightMax - height[right];
right--;
}
}
return res;
}
复杂度
时间复杂度:O(n)
空间复杂度:O(1)
解法四:单调递减栈
思路
这个方法太难了。
栈中的元素是逐渐递减的。只有当元素比栈顶小的时候才入栈(即使当前高度为0,也会入栈)。如果发现元素比栈顶的大,说明可能形成了低洼地带。在算面积时,需要将上一个先出栈,因为只有3个元素才能形成个低洼地带。
代码
public int trap(int[] height) {
int res = 0;
int i = 0;
Stack<Integer> stack = new Stack<>();
while (i < height.length) {
while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
int top = stack.pop();
if (stack.isEmpty()) {
break;
}
int a = i - stack.peek() - 1;
int b = Math.min(height[i], height[stack.peek()]) - height[top];
res += a * b;
}
stack.push(i++);
}
return res;
}