1. 问题描述
我们首先看一下原题目:
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。

输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
我们来分析一下这个题目:
给定一个数组,找出两个下标 i 和 j,使得面积 (j - i) * min(height[i], height[j]) 最大。
根据木桶效应,如果把两条垂线看成两块木板,那么能容纳水的最大容量受限于较短的那根木板。
2. 解题思路
上面的面积公式告诉我们,我们要寻找的最大面积取决于两个因素,一个是底边的宽度,另一个是两个木板中较短的那一个。
最直观的解法还是两层for循环的暴力解法,但是这种 O( <math xmlns="http://www.w3.org/1998/Math/MathML"> n 2 n^2 </math>n2)的时间复杂度,在数据量过大时必然会导致超时。因此,我们需要寻找一种能够排除大量无效状态的方法。
这里,我们设置两个指针left和right,他们分别指向数组的最左边和最右边,此时,底边宽度是最长的,并且我们轻而易举就可以算出这种情况下的面积。
关键在于指针要怎么移动。
就拿上面那张图来举例,初始left和right指向的元素分别是 1 和 7 ,宽是 8,此时面积为 1 * 8 = 8。
假如我们移动比较长的木板,也就是现在right指向的数组元素,我们将right向左移动一位,这时宽减小为 7,而新得到的最短木板的高度绝不会超过上一次,并且可能还会小于上一次的。
因为left指向的元素1的限制,能容纳水的高度绝不会大于 1 ,所以对于left指向1 的这种情况来看,不论right怎么移,都不会得到比初始面积更大的值。原因很简单:因为宽度一定会变小,而高度可能不变,也可能变的更小,所以面积一定会变小。
排除了移动长木板的策略,下面我们应该尝试移动短木板。
对于初始状态,如果我们移动短木板,也就是left向右移动一位,底边宽度依然减小,但是不同的是新的高度有可能比上一次大。
因此,我们得到一个结论:只有移动较短的一块木板,才有可能在宽度不断减小的情况下,通过高度剧增从而让面积增加。
3. 完整代码实现
依据上面的解题思路,我们梳理一下算法流程:
- 初始化
left = 0,right = n-1,max_area = 0。 - 将
left < right作为循环条件。 - 计算当前面积并更新面积最大值
max_area。 - 移动指针,若
height[left] < height[right],则left++,否则right--。这里实现了每次都只改变较短的那块木板。 - 最后返回
max_area。
完整代码如下:
cpp
class Solution {
public:
int maxArea(vector<int>& height) {
//初始化双指针,分别指向最左端和最右端
int left = 0;
int right = height.size() - 1;
int max_area = 0;
while(left < right)
{
//计算当前面积
int curr_area = (right - left) * min(height[left],height[right]);
//更新最大面积
max_area = max(curr_area,max_area);
//移动指针
(height[left]<height[right]) ? left++ : right--;
}
return max_area;
}
};
这种实现,由于左右指针相向而行,每个元素只被访问一次,因此时间复杂度为 O(n)。
本文结束。