LeetCode第11题思悟——盛最多水的容器(container-with-most-water)

LeetCode第11题思悟——盛最多水的容器(container-with-most-water)

文章知识点预告

  1. 双指针遍历技巧;
  2. 移动小指针的分析;
  3. 暴力求解的一种优化:使用空间换时间;
  4. 数学很重要;

题目要求

给定 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器,且 n 的值至少为 2。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/container-with-most-water
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例

在这里插入图片描述
图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

我的思路

要计算结果的实际上是“面积”。所以,我们就需要底和高。设l为左边高度,r为右边高度,输入数组为num,那么目标长方形的高h为num[l]和num[r]的最小值,和中间数据无关;目标长方形的底b就是(r-l)了。

于是问题转换为在输入数组中寻找Min(num[l],num[r])*(r-l)的最大值

我是这样解决的:固定底长b,寻找以b为底的长方形的最高值,将其存在数组中;然后计算出最大值;

代码实现如下:

public int maxArea(int[] height) {
        int bottomMaxWidth=height.length;
        int[] maxHeights=new int[bottomMaxWidth];//比实际的长度多一,为了计算方便
        int currentHeight,currentMax,currentCapacity,maxCapacity=-1;
        for(int i=0;i<height.length;i++){
            for(int j=i+1;j<height.length;j++){
                currentHeight=Math.min(height[j],height[i]);//获得当前矩形的高
                currentMax=maxHeights[j-i];//获得目前以j-i为底长的矩形的最大的高
                if(currentMax<currentHeight){
                    maxHeights[j-i]=currentHeight;//更新
                }
            }
        }
        for(int i=1;i<bottomMaxWidth;i++){
            currentCapacity=i*maxHeights[i];//计算
            if(currentCapacity>maxCapacity){
                maxCapacity=currentCapacity;//更新最大值
            }
        }
        return maxCapacity;
    }

实际上,算是暴力解法(算出所有可能的容量,然后找出最大值)的一点点升级吧:所谓升级是我们没有挨个计算面积,而是缓存对应的最大高度。时间复杂度n^2,空间复杂度n。

该方式当初是超时了的,但是现在提交的时候(486ms),竟然又可以通过了(LeetCode中文网站)。击败13%左右的用户;

优秀解法

public int maxArea(int[] height) {
	int maxarea = 0, l = 0, r = height.length - 1;
     while (l < r){
         maxarea = Math.max(maxarea, Math.min(height[l], height[r]) * (r - l));
         if (height[l] < height[r])
             l++;
         else
             r--;
     }
     return maxarea;
}

时间复杂度n,空间复杂度1

时间大概在6ms左右!

该方法的解题思路和我通过分析得到的思路是一致的:寻找Min(l,r)*(r-l)的最大值。 我们把该值称为目标表达式。但是思路的实现却截然不同。

优秀解法使用了“双指针遍历”的技巧——所谓“双指针遍历”是指从两个指针分别从数组的左右两端开始向中间靠拢,并在靠拢的过程中,完成数组遍历。

我们可以看到,每次针对一组(left,right),都运算了一遍目标表达式,并且更新其最大值,也就是我们要求的结果。之后便移动指针:每次移动高度较小的指针。

当l和r相遇,数组遍历结束,获得目标值。

这里,有两个关键问题需要想明白:

  1. 为什么不移动高度较大的指针(以下简称高指针)?
  2. 为什么移动高度较小的指针一定能获得最优解?

这两个问题的解答才是这道题的精髓所在!读者可以先思考思考~

首先,我们要明白以下几个定论:

  1. 目标矩形的高度,取决于左、右指针所表示高度的最小值;
  2. 在目标矩形的高度相同时,底的长度越长,面积越大;底越小,面积越小;
  3. 我们要找的是最大值;

以上三条,看似是“废话”,实则不然;

**对于第一个问题:因为移动高指针后,新得到的矩形面积将只会变小!也就是我们的计算过程并不能捕捉到最优解。**证明如下:

假设left<right,并且移动了right,记此时right指针为current right。如果 current right表示的高度大于left表示的高度,那么目标矩形的面积因为高度h(取决于left,因为left小嘛)没变,底却变小(因为向中心移动了right嘛)而变小;于是只要移动,就变小。

当left>right而移动left时,道理是一样的。通俗来说,就是移动高指针无法得到答案,所以不移动高指针。

**对于第二个问题:主观来讲,因为移动之后,虽然底变小了,但是因为高度存在变大的可能,所以面积也存在变大的可能,即做法目测是合理的;但是,我们就一定能发现最大值吗?答案是:我们一定会发现。**证明如下:

我们需要注意到:记bottom=right-left。当移动指针后,bottom值就不会再出现——不管是left增大还是right减小,bottom总是变小的。

而从自己的“暴力”解法来看,bottom为某个特定值的矩形实际上有很多,比如(1,2)、(3,4)都是bottom为1的矩形,按照“双指针”遍历这样的操作,真的可以用一个值来代替一系列值吗(指一系列bottom相同的矩形的面积)?

实际上,这是我面对该答案最大的疑惑。解决该疑惑后,就会找到第二个问题的答案~

为了分析方便,我们记此时的计算区间为[left,right];bottom=right-left;输入数组为h,并且h[left]<h[right],即下一次将往右移动left指针,面积为A;移动后,区间变为[left+1,right],面积变为B;

我们说,[left+1,right]代表了长度为bottom-1所有的矩形,那么我们先看看[left,right-1]的矩形B1的面积(它们的底长相同);此时,不论h[right-1]的值为多少,我们知道B1都不是我们要找的最大值:

  1. h[right-1]>h[left],B1的高为h[left]和A的高是相同的,但是A的底要大,所以B1<A,不是最大值;
  2. h[right-1]<h[left],B1的高小于A的高,底也小于A的底,所以B1<A,不是最大值;

由上面的分析我们可以得到,是A排除了B1,B1的出局和B没有关系,即便B1可能大于B,但是不可能大于A,由第三条定论我们可以知道,B1不需要考虑;

看完比较特殊的例子后,我们看看一般的情况:

在这里插入图片描述
我们知道,对于L1,L2,如果L2>L1,那么一定存在某个Rx,h(Rx)>h(L1),即让L1向右移动,这样L才能取到L2;对于R来说也是一样的:如果R1<R2,那么一定存在某个Lx,h(Lx)>h(R1);

由以上结论,可以很方便的得到:对于某个区间[L,R],如果L1<L,那么一定存在Rx,Rx>=R(也就是Rx在R的右边或者就是R本身),且h(Rx)>h(L1);——结论1

Min(a,b)<=a——结论2;

好啦,准备工作完毕!开始证明:在双指针遍历的过程中,如果[l,r]出现,那么其他任何bottom为r-l的矩形,不可能是最大值。

即[l-i,r-i]一定不是最大值。

  1. i>0,则L1=l-i,那么由结论1(区间取[l,r])知道,一定存在Rx,Rx>=r,且h(Rx)>h(L1);也就是此时矩形[L1,Rx]的高hx=h(L1),底bx=Rx-L1>=r-L1>r-i-L1=矩形[l-i,r+i]的底;且矩形[l-i,r+i]的高<=h(l-i)=h(L1)【结论2】,由此,我们知道,此时矩形[L1,Rx]的面积一定大于[L1,r-i]的面积。即[l-i,r-j]一定不是最大值,如果双指针遍历的过程中出现[l,r];
  2. i<0,则区间为[l+a,r+a],实际上,由对称性可知,结论依旧成立:一定存在Lx,Lx<=l,且h(Lx)>h(r+a),矩形[LX,r+a]的高为h(r+a),底r+a-Lx>=r+a-l>r-l=[l+a,r+a]的底;而高<=h(r+a);即此时[l+a,r+a]也不是最大值,如果双指针遍历的过程中出现[l-r];

于是两个问题,均被解决;

差别在哪里

需要注意的是,我们在暴力求解的过程中,根据底边长的不同,通过一个数组来缓存最大值,从而简化了一点点暴力求解;(实际上是用空间换时间的做法);

但是在优秀解法中,我们通过证明,当[l,r]出现时,就排除了所有bottom=r-l的矩形,而通过while循环,则对所有bottom的可能进行了遍历;真的很高明;

我也只是理解了这种解法,想是想不出来的;但是我可以把它记在心里写到小本本里啊。

知识点小结

  1. 双指针遍历技巧;
  2. 移动小指针的分析;
  3. 暴力求解的一种优化:使用空间换时间;
  4. 数学很重要;
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值