42. 接雨水
问题描述:
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例1:
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
思路1(暴力解法):
最常规也是最容易想到的解法,从0 到 i - 1每个位置能够存储的水量由左边最大值和右边最大值做差决定的。所以,我们可以采取最暴力的解法,对于每个位置i,我们都去遍历,记录它左边的最大值max_left
,右边最大值max_right
。左边最大值和右边最大值之间的最小值再减去当前柱子高度即为当前位置所能装水量。
代码:
/**
* 暴力
* */
public int trap(int[] height) {
if (height == null || height.length == 0)
return 0;
int res = 0;
int n = height.length;
for (int i = 1;i < n - 1; i++) {
int max_left = 0 , max_right = 0;
for (int j = i;j >= 0; j--) {
max_left = Math.max(max_left , height[j]);
}
for (int j = i; j < n; j++) {
max_right = Math.max(max_right , height[j]);
}
res += Math.min(max_left , max_right) - height[i];
}
return res;
}
思路2(动态规划):
暴力解法中,我们对每个位置都遍历一次它的max_left
和max_right
,浪费了大量的时间。所以我们可以通过一次遍历,把所有左边的最大值和右边的最大值分别用两个数组记录下来,就可以大大的减小时间。
代码:
/**
* 动态规划
* */
public int trap(int[] height) {
if (height == null || height.length == 0)
return 0;
int n = height.length;
int[] dpleft = new int[n];
int[] dpright = new int[n];
dpleft[0] = height[0];
for (int i = 1;i < height.length; i++) {
dpleft[i] = Math.max(dpleft[i - 1] , height[i]);
}
dpright[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; i--) {
dpright[i] = Math.max(dpright[i + 1] , height[i]);
}
int ans = 0;
for (int i = 0;i < n;i ++) {
ans += Math.min(dpleft[i] , dpright[i]) - height[i];
}
return ans;
}
思路3(辅助栈):
除了这两种解法,我们还可以用一个辅助栈来计算每个元素的左边最大值和右边最大值。如果当前的圆柱小于或等于栈顶的条形块,我们将条形块的索引入栈,意思是当前的条形块被栈中的前一个条形块界定。如果我们发现一个条形块长于栈顶,我们可以确定栈顶的条形块被当前条形块和栈的前一个条形块界定,因此我们可以弹出栈顶元素并且累加答案到 ans 。
代码:
/**
* 栈
* */
public int trap(int[] height) {
if(height == null || height.length == 0)
return 0;
int ans = 0;
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < height.length; i++) {
while ( !stack.empty() && height[i] > height[stack.peek()]) {
int top = stack.peek();
stack.pop();
if (stack.empty())
break;
int distance = i - stack.peek() - 1;
int h = Math.min(height[i] , height[stack.peek()]) - height[top];
ans += distance * h;
}
stack.push(i);
}
return ans;
}