这题挺有意思。最naive的想法就是看每个位置左右两边的bar的高低,然后来决定当前位置能装多少水。有意思的地方在于,每个位置的组左右高度,不是其相邻位置的高度,而是整个左边部分和右边部分的最大值!这是一个最关键的规律,发现了规律,就可以顺利解题了,剩下的事情就是用什么算法来实现,算法的效率复杂度的区别了。
最直接的方法,遍历每一个元素,在扫一遍左右两边,找到左右最大值,然后取较小值,就找到了threshold,然后比较本身和threshold的大小,本身大,则无雨水积累,小则取差值。那么一个n个点,每个点又遍历一次array,有n次操作,所以复杂度是N平方。具体代码就不写了。
基于上面的思路,想想优化的方法。其实每个元素的左边的最大值,可以直接用到下一个元素的最大值,通过比较maxLeft[i] 和 height[i] 就可以得到了,完全没有必要再遍历一遍。所以通过新建一个array 来记录每一个位置的maxLeft就可以了。
同样的,可以再建立一个array从右往左找到maxRright,然后把两个array读一遍,去min得到threshold,再比较和当前的height的高低,计算得到每个位置的水位。复杂度是3N,因为扫了3次array,测试后的效率为67%
public class Solution {
public int trap(int[] height) {
int len = height.length;
int[] maxLeft = new int[len]; // 建一个array来存放maxLeft
int max=0; // 这里设置成0似乎和max的意思不一致,实际上是一致的,要准确把握题意,Y轴不算是一个bar,也就是说如果第一个位置是0,那么左边就一直不会有积水,看题中的图就理解。
for(int i=0; i<len; i++){
maxLeft[i]=max; // [i]位置的大小取决于[i-1],那么当i==0时,就要给定一个初始值,就是0
max=Math.max(max, height[i]); // 然后通过height[i]更新maxLeft,为下一个位置[i+1]准备,这样设置循环就很完美
}
int[] maxRight = new int[len]; // 同理,再建一个array来村反maxRight
max=0;
for(int i=len-1; i>=0; i--){
maxRight[i]=max;
max=Math.max(max, height[i]);
}
int water=0;
for(int i=0; i<len; i++){
int diff=Math.min(maxLeft[i], maxRight[i])-height[i];
if(diff>0) water+=diff;
}
return water;
}
}
上面的方法其实还可以再优化,那就是在从右往左的过程中,其实就可以计算出累加的水的部分了,因为所有的maxLeft都ready了,每得到一个maxRight就可以得到threshold然后得到累加的水位。复杂度是2N,效率有所提升,77%
public class Solution {
public int trap(int[] height) {
int len = height.length;
int[] maxLeft = new int[len]; // 建一个array来存放maxLeft
int max=0; // 这里设置成0似乎和max的意思不一致,实际上是一致的,要准确把握题意,Y轴不算是一个bar,也就是说如果第一个位置是0,那么左边就一直不会有积水,看题中的图就理解。
for(int i=0; i<len; i++){
maxLeft[i]=max; // [i]位置的大小取决于[i-1],那么当i==0时,就要给定一个初始值,就是0
max=Math.max(max, height[i]); // 然后通过height[i]更新maxLeft,为下一个位置[i+1]准备,这样设置循环就很完美
}
int water=0;
int maxRight=0;
for(int i=len-1; i>=0; i--){
int diff=Math.min(maxLeft[i], maxRight)-height[i];
if(diff>0) water+=diff;
maxRight=Math.max(maxRight, height[i]);
}
return water;
}
}
看了看别人的解法,跟我的思路也类似,区别在于,在从右往左的过程中,他没有new出新的variable,只是都存放在原有的container空间里,效率有90%,代码如下:
public class Solution {
public int trap(int[] height) {
int len = height.length;
int[] container = new int[height.length];
int maxLeft=0; // 这里设置成0似乎和max的意思不一致,实际上是一致的,要准确把握题意,Y轴不算是一个bar,也就是说如果第一个位置是0,那么左边就一直不会有积水,看题中的图就理解。
for(int i=0; i<len; i++){
container[i]=maxLeft; // [i]位置的大小取决于[i-1],那么当i==0时,就要给定一个初始值,就是0
maxLeft=Math.max(maxLeft, height[i]); // 然后通过height[i]更新maxLeft,为下一个位置[i+1]准备,这样设置循环就很完美
}
int water=0;
int maxRight=0;
for(int i=len-1; i>=0; i--){
container[i]=Math.min(maxRight, container[i]); // 这里container记录的不是右边的最大值,而是:左右最大值中的较小值,就是为了找到关键的threshold。
// 而右边的最大值只需要通过一个参数来记录即可,因为scan过这一遍后,就得到结果了,没必要保存每个位置的右最大值。如下;
maxRight=Math.max(maxRight, height[i]); // 当前的height[i]是为了下一个位置[i-1]的更新做准备
if(container[i]>height[i]) water+=container[i]-height[i];
}
return water;
}
}