给你 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
题目链接:中文题目;英文题目
直接遍历所有的数对,然后求最大的面积,应该是第一眼就能想到的方法,只是这个方法时间复杂度为O(n^2),当数组包含大量数字时,该方法超时。
class Solution {
public:
int maxArea(vector<int>& height) {
int maxArea = 0, len = height.size();
for (int i = 0; i < len - 1; i++) {
for (int j = i + 1; j < len; j++) {
maxArea = max(maxArea, (j - i) * min(height[i], height[j]));
}
}
return maxArea;
}
那我们能不能设置左指针(left = 0),和右指针(right = 数组长度 - 1),然后从外向里找到最大的面积呢?回答这个问题,我们需要解决两个:1. 怎么移动左右指针;2. 这种移动方法为什么一定能找到最大面积,而不会跳过它?
首先,我们把面积(S)公式总结一下。对于i和j位置代表的木板高度ai和aj,我们有如下的面积公式:
S = (j - i) * min(ai, aj);i < j
此时,因为面积取决于ai和aj两者之中最小的那个,所以我们要尽可能的让ai和aj尽可能的取到更大的值,所以这种思路下,如果ai < aj,我们让i往右走,即i + 1,让ai尽可能大;反之,当ai >= aj,我们让j往左走,即j - 1,让aj尽可能大;但是,我们就遇到了两个问题:1)j - i的距离如何保证最大?2)为什么不能反方向走呢?
既然如此,我们把上述两种情况分别讨论:
1.ai < aj
首先,利用条件ai < aj,S = (j - i) * min(ai, aj) = (j - i) * ai。这种情况下,我们假设i往右走,即i + 1,那为什么不让j往左走呢?即j - 1,我们假设一下j - 1,也有两种情况:
当ai <= aj - 1:
S1 = (j - i - 1) * min(ai, aj) = (j - i - 1) * ai;
当ai > aj - 1:
S2 = (j - i - 1) * min(ai, aj) = (j - i - 1) * aj - 1;
我们可以看到前部分(j - i - 1)一定比(j - i)小,所以当ai <= aj - 1,S > S1;当ai > aj - 1,S > S2;因而无论aj - 1大于等于还是小于ai,S都是大于等于新的面积,所有j没有必要往左走。
2.ai >= aj
这里可以用和面上相同的方法得到:无论ai + 1大于等于还是小于aj,S都是大于等于新的面积,所以此时i没有必要往右走。
所以经过上述证明,使用双指针的方法,一定可以找到最大面积。
class Solution {
public:
int maxArea(vector<int>& height) {
int maxArea = 0, left = 0, right = height.size() - 1;
while (left < right) {
maxArea = max((right - left) * min(height[left], height[right]), maxArea);
if (height[left] > height[right]) right--;
else left++;
}
return maxArea;
}
};