题目:盛水最多的容器
给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器,且 n 的值至少为 2。
图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
题目解法有暴力法和双指针法两种,其中暴力法较简单,直接上代码:
class Solution {
public:
int maxArea(vector<int>& height) {
int num = height.size(), maxnum = 0;
if (num < 2) return 0;
for (int i = 0; i < num ;i++)
for (int j = i; j < num; j++) {
maxnum = min(height[i], height[j]) * (j - i) > maxnum ? min(height[i], height[j]) * (j - i) : maxnum;
}
return maxnum;
}
};
这里重点介绍双指针法。
暴力法虽然简单,却需要的时间复杂度,浪费了大量时间。采用双指针法,最重要的一点是两线段之间形成的区域总会收到其中较短一条的限制。此外,盛水面积的大小也和两线段之间的距离有关。
双指针法在由线段构成的数组中使用了两个指针,一个在开头,一个放在末尾。为了使面积最大化,我们在每一步中会找出指针所指向的两条线段形成的区域面积,更新最大面积,并将指向较短线段的指针向较长线段那段移动一步。如果我们试图将指向较长线段的指针向内侧移动,矩形区域的面积将受限于较短的线段而不会获得任何增加。但是,在同样的条件下,移动指向较短线段的指针尽管造成了矩形宽度的减小,但却可能会有助于面积的增大。因为移动较短线段的指针会得到一条相对较长的线段,这可以克服由宽度减小而引起的面积减小。
如果使用矩阵表示以上过程,会更容易理解这个问题。
定义一个矩阵,其中,行表示开头的指针l,列表示末尾的指针r。使用n = 6的矩阵作为范例:
在下图中,x表示我们不需要计算l和r围成的面积,其满足的条件如下:
- 对角线上,l = r;
- 左下角的三角矩阵,l > r
我们从面积(1,6)开始计算,使用o表示。如果左边的线段l比右边的线段r短,矩阵第一行(1,6)左边的元素就不用考虑了,因为它们围成的面积一定小于(1,6)的面积,因为它们的高度一定小于等于l的高度,宽度一定小于(1,6)。
接下来移动l,计算(2,6),如果右边的比较短,(2,6)下方的点就不用考虑了。
不管o的路径是怎样的,我们最终只需要找到这条路径上的最大值,它包含n-1种情况。
经过证明,使用双指针法的时间复杂度是。
class Solution {
public:
int maxArea(vector<int>& height) {
int num = height.size(), maxnum = 0;
int l = 0, r = num - 1;
while(l < r) {
int temp = min(height[l], height[r]) * (r - l);
maxnum = temp > maxnum ? temp : maxnum;
if (height[l] < height[r])
l++;
else
r--;
}
return maxnum;
}
};