11. 盛最多水的容器
1.暴力枚举
-
最容易想到直接暴力枚举,会进行 C n 2 C_{n}^{2} Cn2 次枚举,面积运算公式为$S=(r-l)*min( h[l],h[r] ) $、时间复杂度O(n2);
-
这里的代码会超时,但是会帮助我们下面对于双指针的理解。
-
如下图,以有八根木棍为例,事实上我们只进行了(8*8)/2次的计算.
-
class Solution { public: int maxArea(vector<int>& height) { int n=height.size(); int result=0; for(int i=0;i<n;i++){ for(int j=i;j<n;j++){ int area=min(height[j],height[i])*(j-i); result=max(result,area); } } return result; } };
2.双指针法
-
这里我们可以使用双指针法,从左右两端不断向内收缩,时间复杂度O(n)得出结果:
- 方法:初始左指针l=0,r=n-1;每次向内移动h[l],h[r]中较小的那一个;
-
难点:双指针法的写法也很简单,问题的关键是我们要证明双指针法的正确性,这里力扣的官方题解我认为有瑕疵;
-
官方题解给出的部分其实也很好理解:
对于l,r中较短的那一侧,我们假设此时l是高度较短的那一侧:
1.无论另一侧的r怎么向内移动,min[h[l],h[r]]都不会比当前的数值更大了,因为当前的l就是“短板效应”里最短的那根板;
2.而(r-l)因为双侧指针是不断向内移动的,(r-l)也只会越变越小;
3.因此当前的min[h[l],h[r]]*(r-l)就已经是当前这个位置的 l 可以取得的最大值了;
我们将这个值与结果进行比较取最大值后,抛弃这一个较短的一侧,向内移动后再进行比较。
-
这里分析的问题在于,忽视了l和r靠外的两侧会出现更大结果的可能性;
就上例而言,l是高度较短的一侧,却只允许r向内移动,难道向外移动就不会有更大的结果吗?
———————————————————————————————————————————————
-
对这个问题,需要根据每次比较对搜索空间减小的作用来分析。
我们需要搜索的空间如暴力枚举中的图片一样,需要进行(n*n)/2次检查,但是每次排除短端都能将搜索空间减小一行或者一列,而暴力枚举每次只能排除一个。
我们假设第一次左侧l=0是较短的一端,无论r在搜索空间内的哪个位置,l=0可能的最大面积我们可以通过上面的公式一步计算出来。我们再将其排除,事实上是排除了l=0作为最终答案的所有可能:如下图:
此时新的搜索空间就是由新的[l,r]构成的新的倒三角,l,r两端外侧的可能性都被排除了,因此,双指针法只需要不断向内移动的正确性得以证明。
-
我们再模拟一步,如果是右侧r是端端被排除,搜索空间被缩小了一列,新的搜索空间将r右侧是最终答案的可能性彻底排除:
class Solution { public: int maxArea(vector<int>& height) { int n=height.size(); int result=0; int l=0,r=n-1; while(l<r){ int area=min(height[l],height[r])*(r-l); if(height[l]<=height[r]){ l++; }else{ r--; } result=max(result,area); } return result; } };