Leetcode-11 Container With Most Water

题目:

                                            《Leetcode-11 Container With Most Water》

示例输入:                                                                                                                                          示例输出:

6                          (表示要输入的线段的个数)                                                                                 16

1 1                       (之后的每行第一个参数表示下标,第二个参数表示长度)

4 2

5 4

7 8

9 5

10 2

 分析:

 对于本题,首先能想到最简单的方法,就是使用两重循环,找到每两个线段的组合,也就是找到所有的”容器”,然后最后取容积最大的容器即可,但是如果仅仅是这么简单,那么本篇博客也就没啥含量了,下面要介绍的方法是仅仅使用时间复杂度为O(N)的方法.

首先我们规定i,j是容器的左右臂所在的下标,那么我们最终的目的就是求出下面公式的结果:

                         max{min{a[i], a[j]} * (j – i)}        (i<j)

  我们使用”两头扫的方法”,使得一开始i,j分别指向最两边的线段,然后i,j不断向中间靠近,那么问题就来了,i,j每次要怎么向中间靠近,是舍弃较短的线段的下标还是舍弃较长线段的下标.其实简单想一下,就会”感觉”肯定保留长的线段,将短的下标向中间移动.那么问题就来了,为什么这种方法是正确的呢?

  首先,我们假设,当前的i,j分别指向下标为n,m的线段,即i=n,j=m,那么此时构成的容器的容积为min(a[n],a[m])*(m-n),如果a[n]<a[m],这个时候我们舍弃a[m],将j指向中间的线段q,也就是如下图:

                                 《Leetcode-11 Container With Most Water》

我们首先可以获得以下几个式子:

1.h1=min(a[n],a[m])=a[n]

2.h2=min(a[n],a[q])<=a[n]

  其中h1,h2分别是移动j之前和之后容积的高度,显然,h2<=h1,同时我们可以知道,移动后的容器更”窄”了,那么移动后的容器的容积就更小了,所以用这种移动方法的话,获得的容器的容积不断减小,那么我们最终获得的结果肯定就是一开始时i指向第一个线段,j指向最后一个线段,这种方法显然是不可以的.

既然这样,那我们就保留长的线段,移动短的线段,首先我们大概算一下这种方法的可行性,如下图的移动方式:

                                                 《Leetcode-11 Container With Most Water》

那么我们同样可以获得公式:

1.h1=min(a[n],a[m])=a[n]

2.h2=min(a[p],a[m])<=a[m]

  而a[n]<=a[m],所以移动后的容器的高度h2是有可能高于移动之前的高度的,这样在容器变窄之后,才有可能使得容器容积变大.

当然我们只是粗略的证明下,那么如果证明我们获得的结果就是最优解,也就是最大的容器,这样的粗略证明显然是不够的。那么如何才能证明上述的方法肯定是扫过最优解呢?

  首先我们假设最终的最优解,也就是最大容器的左右线段的下标分别是T,Y,那么在i,j不断向中间靠近的时候,肯定有一方先到达最优解的线段,即有可能i先到T,或者j 先到Y,a[T]和a[Y]的大小关系没有影响,我们就假设j 先到Y,也就形成了以下的情形:                                                                                                                                                          《Leetcode-11 Container With Most Water》

  首先要知道的是,在j已经移动到Y时,则在i移动到T之前,a[i]肯定是小于min(a[T],a[Y]),也就是最优解的容器的高的,因为i,Y的距离变窄,只有i小于高,那么才能保证最优解的容器的容积大于当前a[i]和a[Y]构成的容器的容积的,那么在a[i]<min(a[T],a[Y])<=a[Y]时,使用我们的方法,不断将i向中间移动,在i<Y时,移动能保证移动到i=T,那么就能保证扫过最优解。

  同理,我们如果使用舍弃长线段,保留短线段的错误方法后,当j已经移动到最优解Y时,在a[i]<a[Y]时,我们会移动j,这样我们就会”完美”错过最优解,这样也就同时证明了之前舍弃长线段的方法的错误性.

  下面附上源码:

#include<iostream>
using namespace std;
#define Max 10000
int height[Max];
int N;
int solve(int last)
{
	int MaxArea=0;
	int left=0,right=last;
	while(left<right)                          //从两头扫, left,right分别表示当前水桶的左右壁的下标 
	{
		int currentArea=min(height[left],height[right])*(right-left);  //取左右壁中较小的作为水桶高度,取下标之差作为底边面积 
		MaxArea=max(MaxArea,currentArea);
		height[left]<height[right]?left++:right--;    //每次将较小壁向中间移动 
	}
	return MaxArea;
}
int main()
{
	cin>>N;
	int last=-1;
	for(int i=0;i<N;i++)
	{
	   int index,value;
	   cin>>index>>value;
	   height[index]=value;
	   last=max(last,index);
	}
	
	cout<<solve(last);
	return 0;
}

PS:分析解释了这么多,算法的实现代码却很少,这也许就是算法之美吧!

 

点赞