[LeetCode] Container with most water

这几天刷leetcode,才做了几道题,就被打击的自信全无。各种拼写错误,各种边界值问题,被折磨的死去活来。。。分享一道简单的,我会写的,提升下士气。。。

题目:在xy座标平面上,有一串点(1,a1),(2,a2),(3,a3),…(n,an). 任意两点与x轴的连线再加上x轴,都可以构成一个三条边的桶形。求最大的桶能装多少水。。。

第一眼看到题目的思路是, f(i,j)=(ij)min(a[i],a[j]) ,求 max(f(i,j))
按照公式,两个循环搞定。无空间复杂度,时间复杂度O(n2)。

但是,这解法也太简单粗暴了。如果只是为了能跑起来,这样还行得通,但性能上实在是有点惨。
脑海中想象一下图形,可以发现,对于固定的a[i],利用数轴的特性,
如果 a[j]<a[j+1] ,那么 f(i,j)<f(i,j+1)

简单优化一下,代码如下

public class Solution {
    public int maxArea(int[] height) {
        int max = 0;
        int length = height.length;
        for(int i=0; i< length; i++){
            for(int j=i+1; j<length; j++){
                int b = height[j];
                //如果下一个数值比本次的更大,就没必要计算max,直接跳过。
                if(j+1<length){
                    if(height[j+1] >= b){
                        continue;
                    }
                }

                int a = height[i];
                max = Math.max(max,(j-i)*Math.min(a,b));
            }
        }
        return max;
    }
}

假设,数值是随机的,后一个数字比前一个数字大的概率是50%。
性能的话,明显多加了o(n2)次的判断,减少了差不多一半的sum的计算。但是,提升不明显啊。

仔细分析一下程序的执行过程,对于任何一个i,循环j要执行length-i次。即使,明明已经知道了某个 a[j]<a[j+1] ,但是循环的时候还是会再走n遍。也就是说,经过第一次遍历,我们其实已经知道了在a[j]到a[j+m]的区间内,取a[j+m]来计算f()就足够了,但是循环的时候,我们还是从a[j]走起了。

因此,进一步的优化思路为,去掉这些重复的调用。简单的说,记录一个数值,每次j循环不再continue的时候,把这个值记录下来,作为下次循环的初始值。
对于比较随机的数列,这样子已经可以优化的不错了。最好的情况下,j循环只需要O(n)就完成了,但最坏情况还是会被调用O(n2)次。。。

public class Solution {
    public int maxArea(int[] height) {
        int max = 0;
        int length = height.length;
        int maxj = 0;
        for(int i=0; i< length; i++){
            if(maxj < i){
                maxj = i;
            }
            for(int j=maxj; j<length; j++){
                int b = height[j];
                //如果下一个数值比本次的更大,就没必要计算max,直接跳过。
                if(j+1<length){
                    if(height[j+1] >= b){
                        continue;
                    }
                    //记录当前j到达的位置,下次直接从此位置开始计算。
                    maxj = j;
                }

                int a = height[i];
                max = Math.max(max,(j-i)*Math.min(a,b));
            }
        }
        return max;
    }
}

根据固定i变动j的思路,只能优化成这样了。但是,转念一想,如果固定j变动i,也是可以的。实际上,i和j是完全对等的。
所以,对于固定的a[j]
如果 a[i]<a[i1] ,那么 f(i,j)<f(i1,j)

总结一下思路,
如果 a[jt]<a[j] ,那么 f(i,jt)<f(i,j)
如果 a[i+t]<a[i] ,那么 f(i+t,j)<f(i,j)
在座标轴上,可以想象为,从两端开始算起,有且只有找到能有效提高height的数值时,才可能大于之前的盛水量。
那么,把两个公式结合起来用一下
如下:

public class Solution {
    public int maxArea(int[] height) {
        int max = 0;
        int length = height.length;
        int h;
        for(int i=0,j =length-1; i<j;){
          h = Math.min(height[i], height[j]);
          max = Math.max(max, (j-i)*h);
          //直到找到比当前高度更高的i和j,才进行一次计算
          while(h >= height[i] && i<j){
              i++;
          }
          while(h >= height[j] && i<j){
              j--;
          }
        }
        return max;
    }

时间复杂度降低到O(n)了。这里的h起到了之前的maxj的标记值的作用。

点赞