题目描述
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明:你不能倾斜容器。
示例 1:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例 2:
输入:height = [1,1]
输出:1
提示:
n == height.length
2 <= n <= 105
0 <= height[i] <= 104
分析
本题考察双指针,10w级别的数据规模意味着暴力枚举会TLE,只能选择平方级别以下的算法求解。在做本题前,可以先做下AcWing 131 直方图中最大的矩形,这两题既有相似之处又有不同之处。
求直方图的最大矩阵面积,需要连续的柱子构成矩形,以矩形i的高度h[i]作为对齐矩形的高度想要得到最大的对齐矩形面积,当且仅当对齐矩形的宽度最大,即第i个矩形向左向右延伸到小于它高度的矩形为止,也就是说,只要在序列里找到离其左边和右边小于该元素的元素中离它最近的元素即可,这就是单调栈的经典应用,找一个元素左边离它最近的小于它的元素。
再来看本题,求容器可以容纳的最大水量,只要左右的柱子足够高,中间的矮柱子就可以忽略,如果我们像求直方图的最大矩形面积那样来划分本题的状态,还是以每根柱子的高去当做容器的高,求此时的最大容量。比如高度数组是1 3 4 2 5 3 1,那么容器高度是2时,最大的容量就是2向左延伸到比它大的最左边元素3和最右边元素3,用两个3的距离乘上高度2,就求出了此时的容器的最大面积。我们枚举下容器的高度,最后在能够构成的最大容量里取个max就是问题的解了。还是和直方图的最大矩形面积比较下,求最大面积需要找到每根柱子左边离它最近比它小的柱子的位置。而本题需要求每根柱子比它大的最左边和最右边元素的位置。其实也是可以用单调栈求解的,对于左边的柱子,越靠左,高度越大就更容易作为最终答案的高度,所以可以维护一个高度递增的单调栈,还是以1 3 4 2 5 3 1为例,开始1入栈,然后3入栈,4入栈,遇见2时由于2左边有比它更大的元素,所以2不需要入栈保存,能够延伸到2的,一定能够继续向左延伸,这时候栈里面元素是1 3 4,需要找到最左边的不比2小的元素,二分下就可以找到3.对于2右边的不小于它的最右边元素,也可以倒着来个单调栈,这种做法的时间复杂度是O(nlogn)的,也是可以解决本题的。一般单调栈的算法线性的时间复杂度就可以解决了,是因为单调栈往往用于求离每个元素最近的比它大或者小的元素,而本题却是求离它最远的比它大的元素,所以需要在单调栈的基础上继续二分。
下面分析本题的线性复杂度的解法。我们还是以每根柱子的高度作为容器的高度来枚举容器的最大容量。初始时l指向最左边柱子,r指向最右边柱子,以本题的用例[1,8,6,2,5,4,8,3,7]为例。左指针指向1,右指针指向7,那么以这两根柱子构成的容器容量就是1 * 8 = 8,也就是l和r指向的柱子中较短的那根作为容器的高度,再乘上r - l作为宽度,就是容器的容量了。注意此时对于l指向的1而言,其能够向右延伸的最右位置就是7所在的位置了,所以以1作为容器高度来枚举容器最大容量的任务已经完成了,7向左延伸是不可能延伸到1的,因为需要延伸到最左边不比它小的位置,所以对高度为7的枚举也用不到1,如果中间存在一根高度小于等于1的柱子,向左延伸的确可以到达1,但是1其容量必然不可能大于1和7构成的容量了。这意味着l和r执行的两个元素,较小的那个 已经完全失去了作用,所以l可以右移至8,此时r还是指向7,可以再计算下此时容器的容量,然后对指向较小的元素7的指针r左移一位,以此类推,直至l和r重合。这就是本题双指针的解法。
本题的暴力解法可以按顺序枚举容器的两边,也可以枚举以每根柱子作为容器高度,然后向左向右枚举容器的宽度,双指针解法里面的l和r恰好是容器高度是min(l,r)时,两边能够延伸到的极限就是l和r两个位置的情况,相当于巧妙的提高了暴力枚举的效率。
代码
class Solution {
public:
int maxArea(vector<int>& height) {
int n = height.size();
int l = 0,r = n-1;
int res = 0;
while(l < r){
if(height[l] < height[r]){
res = max(height[l] * (r - l),res);
l++;
}
else{
res = max(res,height[r] * (r - l));
r--;
}
}
return res;
}
};