给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
输入示例: [0,1,0,2,1,0,1,3,2,1,2,1]
输出示例:6
题目及图片来源于Leetcode
Java函数形式: public int trap(int[] height)
初看问题,觉得非常复杂,因为影响水量的因素有很多,不知如何去考虑。那是因为我们还没有确定一个具体的方向,所以会感觉很乱。
我们的第一步就是确定一个方向,按照什么标准去计算?
这里介绍第三种思路,按照列去进行计算。
我们再来看第二个问题,如何计算每一列的存水量?
于是我们可以开始简单的去模拟这个过程:
代码如下:
class Solution {
public int trap(int[] height) {
int ans=0;
int n=height.length;
for(int i=0;i<n;i++){
int left=0,right=0;
for(int j=i;j>=0;j--){
left=Math.max(left,height[j]);
}
for(int k=i;k<n;k++){
right=Math.max(right,height[k]);
}
ans+=Math.min(left,right)-height[i];
}
return ans;
}
}
O(N^2)
O(1)
我们可以发现内存循环的每一次循环的目的都只是找出最大值,这里面存在了大量的重复运算,我们可以提前把这些计算出来,然后直接拿来使用,做到循环并行而不是嵌套。
这其实就是动态规划的思路:
left[i]=Math.max(left[i-1],height[i-1]);
right[i]=Math.max(right[i+1],height[i+1]);
代码如下:
class Solution {
public int trap(int[] height) {
int ans=0;
int n=height.length;
int[] left=new int[n];
int[] right=new int[n];
for(int i=1;i<n-1;i++){
left[i]=Math.max(left[i-1],height[i-1]);
}
for(int i=n-2;i>=0;i--){
right[i]=Math.max(right[i+1],height[i+1]);
}
for(int i=1;i<n-1;i++){
int tmp=Math.min(left[i],right[i])-height[i];
if(tmp>0) ans+=tmp;
}
return ans;
}
}
O(N)
O(N)
我们从代码中可以看到,其实每次都只使用了dp
一个值,按照dp
的常规优化,我们可以把left
用一个变量替代,right
暂时不能,因为遍历的方向不同。
代码如下:
class Solution {
public int trap(int[] height) {
int ans=0;
int n=height.length;
int left=0;
int[] right=new int[n];
for(int i=n-2;i>=0;i--){
right[i]=Math.max(right[i+1],height[i+1]);
}
for(int i=1;i<n-1;i++){
left=Math.max(left,height[i-1]);
int tmp=Math.min(left,right[i])-height[i];
if(tmp>0) ans+=tmp;
}
return ans;
}
}
O(N)
O(N)
O()N
实际上比上一段代码是有优化的,省去了一个数组。我们从上段代码的优化思路可以得到,我们之所以不能优化的原因是因为遍历方向不同,那么,我们可不可以在一段循环中用两种方向遍历呢?
答案是肯定的,我们可以使用双指针,从两端开始遍历,双指针相遇就结束循环,那么问题来了,什么时候正向遍历,什么时候反向遍历?
height[left]向左端遍历,反之,向右端遍历。
ans+=Math.min(left,right)-height[i];
可以得到,决定遍历方向的是左右最大高度谁最小。class Solution {
public int trap(int[] height) {
int ans=0;
int n=height.length;
int left=0,right=n-1;
int lmax=0,rmax=0;
while(left<right){
if(height[left]<height[right]){
if(height[left]>lmax) lmax=height[left];
else ans+=lmax-height[left];
left++;
}
else{
if(height[right]>rmax) rmax=height[right];
else ans+=rmax-height[right];
right--;
}
}
return ans;
}
}
O(N)
O(1)
Leetcode-4.4每日一题打卡完毕!
心情日记:当你并不只为了自己时,你没有退缩的理由!!!
ATFWUS --Writing By 2020–04-04