题目
给你 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。
示例:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解答
我们采用双指针方法:
class Solution {
public int maxArea(int[] height) {
int left = 0;
int right = height.length - 1;
int maxArea = 0;
while (left < right) {
maxArea = Math.max(maxArea, Math.min(height[right], height[left]) * (right - left));
if (height[left] > height[right]) {
right--;
} else {
left++;
}
}
return maxArea;
}
}
或者再优化一些,思考下如果 height[right] > height[left]
,那么 left 指针需要往右移动,但是如果想要保证总面积比之前大,一定要保证height[left新] > height[left旧]
,这样才有可能出现总面积比之前大的可能,同时在保证时间复杂度是O(n)不变的前提下减少乘法运算。
class Solution {
public int maxArea(int[] height) {
int left = 0;
int right = height.length - 1;
int maxArea = 0;
while(left < right) {
maxArea = Math.max(maxArea, Math.min(height[left], height[right]) * (right - left));
if(height[right] > height[left]) {
while(left < right) {
left++;
if(height[left] > height[left - 1]) {
break;
}
}
}else {
while(left < right) {
right--;
if(height[right] > height[right + 1]) {
break;
}
}
}
}
return maxArea;
}
}
分析
为什么双指针的做法是正确的?换句话说:
数组中所有的位置都可以作为容器的边界,因为我们还没有进行过任何尝试。在这之后,我们每次将 对应的数字较小的那个指针 往 另一个指针 的方向移动一个位置,就表示我们认为 这个指针不可能再作为容器的边界了。
为什么对应的数字较小的那个指针不可能再作为容器的边界了?
leetcode官方虽然给出证明了,但是比较繁琐,我这里用小学生都会的反证法证明下:
前提条件:左侧边界顶点坐标 (left, h0), 右侧边界顶点坐标(right, h1), 左侧比右侧低即:h0 < h1
假设存在 中间边界坐标 (mid, h2), 其中 left < mid < right, 使得左侧边界与其围成的面积大于左侧边界与右侧边界围成的面积(隐含条件是 h2 > h1)
那么有:
(mid - left) * h1 > (right - left) * h1
=> mid - left > right - left
=> mid > right
这与假设 left < mid < right 不符
所以可得原假设不成立
这样的话,利用反证法我们可以轻松得出双指针思想是正确的结论~