#42. 接雨水
2020/4/4每日一题打卡√(通过的第一道困难题)
参考题解:单调栈O(n) --sweetiee
接雨水问题的超完全手册
题目描述
解题思路
1、万能暴力法
思路也很清楚,按列求出该列能接住的雨水,然后再累加。
求每一列雨水的方法:该列能接的雨水受两边最大的高度影响,等于两边最大高度的最小值减去本身值。
public int trap(int[] height) {
int re = 0,left = 0,right = 0;
for(int i = 0;i < height.length;i++){
int p = i-1,q = i+1;
left = right = height[i];
//分别找出左边和右边的最大值
while(p >= 0 || q < height.length){
if(p >= 0 && height[p] > left)
left = height[p];
if(q < height.length && height[q] > right)
right = height[q];
p--;q++;
}
int cur = Math.min(left,right) - height[i];
re += cur ;
}
return re;
}
提交结果:(O(n2)果然很慢)
2、动态编程
在暴力法中,对每个元素都要进行向左向右最大值的搜索,这个过程其实有很多重复比较,可以利用动态规划的思想,从第一个元素开始,保存前一个元素两边的最大值,下一个元素只要与前一个元素的最大值比较就能得到新的最大值。
public int trap(int[] height) {
int re = 0,n = height.length;
int left[] = new int[n],right[] = new int[n];
if(n == 0)
return 0;
left[0] = height[0];right[n-1] = height[n-1];
for(int i = 1;i < n;i++)//找出当前元素左边的最大值
left[i] = Math.max(height[i], left[i-1]);
for(int i = n - 2;i >= 0;i--)//找出当前元素右边的最大值
right[i] = Math.max(height[i],right[i+1]);
for(int i = 0;i < n;i++)
re += Math.min(left[i],right[i]) - height[i];
return re;
}
提交结果:
复杂度分析:
时间复杂度O(n),从前往后遍历数组
空间复杂度O(n),需要额外的数组空间来保存最大值
3、单调栈
这道题其实真正考察的是单调栈解题的思想。
单调栈顾名思义就是栈里面的元素按照递增或者递减的顺序排列
这个有点难懂,我看别人的题解才明白,所以代码里写了很多注释,希望以后还能记得为什么这么写
public int trap(int[] height) {
if(height == null)
return 0;
int re = 0;
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < height.length; i++) {
//当栈非空,而且当前元素大于栈顶元素则出栈(栈里面存放的是下标
while(!stack.isEmpty() && height[i] > height[stack.peek()]){
int h = height[stack.pop()];
//h保存出栈的栈顶元素值,新栈顶此时是左边界 //如果栈内元素一直相同就一直出栈,这个也可以省略不写
while(!stack.isEmpty() && height[stack.peek()] == h)
stack.pop();
if(!stack.isEmpty())
//如果出栈后栈还不为空,Math.min(height[stack.peek()], height[i])求的是左右边界最小的那个,
//-h则是求能放下多少水量,还要乘以长度
re += (Math.min(height[stack.peek()], height[i]) - h)*(i - stack.peek() - 1);
}
stack.push(i); //如果是按照顺序则入栈
}
return re;
}
提交结果:
复杂度分析:
按道理每个元素最多入栈一次,时间复杂度应该是O(n)的,但是这种方法实际提交时间表现却不太好,可能是因为出入栈操作有点费时间?
4、双指针法
public int trap(int[] height) {
int re = 0,i = 0,j = height.length - 1,leftmax = 0,rightmax = 0; //左边最大值和右边最大值
while(i <= j) {
if(leftmax < rightmax) { //如果左边最大值小于右边最大值
leftmax = Math.max(leftmax, height[i]); //向右递推求左边最大值
re += leftmax - height[i++];
}
else {
rightmax = Math.max(rightmax, height[j]);
re += rightmax - height[j--];
}
}
return re;
}