题目
给你 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
来源:力扣(LeetCode)
输入:[1,8,6,2,5,4,8,3,7]
输出:49
链接:https://leetcode-cn.com/problems/container-with-most-water
解题
暴力解法
依次枚举出所有的情况,求出盛水面积的最大值。
盛水面积 = min(height[left],height[right]) X (right - left)
时间复杂度: O(n2)
空间复杂度: O(1)
class Solution {
public int maxArea(int[] height) {
//暴力解法
int n = height.length;
int res = 0;
for(int i = 0; i < n-1; i++){
for(int j = i+1; j < n; j++){
res = Math.max(res, Math.min(height[i],height[j])*(j-i));
}
}
return res;
}
}
双指针解法
思路
已知盛水面积的求法,需要优化的就是怎么遍历。很显然枚举是很浪费时间的,我们可以枚举的过程中根据某些条件做出最优选择,舍弃掉一些明显不可能的选项可以不再去遍历,节省时间。
双指针就是这么一种优化后的遍历方法,初始左指针在最左边,右指针在最右边。因为所求的盛水面积是min(left-height,right-height)*(right-left)的最大值。因为尽量使left-height和right-height都更高一点,能有利于整个乘积的高。故:
- 如果left-height < right-height,则将左指针右移以寻找更高的左指针使得在有面积更大的可能性里寻找答案。
- 如果right-height < left-height,则将右指针左移以寻找更高的右指针进而寻找面积更大的情况。
时间复杂度: O(n),双指针只需要遍历一遍数组
空间复杂度: O(1)
双指针的正确性证明
如果正向不太能理解,我们可以反向证明。
双指针代表的其实是 可以作为容器边界的所有位置的范围。初始条件双指针指向数组的左右边界就是将整个 组中所有的位置都可以作为容器的边界。接下来双指针的移动过程就是一步步抛弃不可能的边界点的过程,抛弃一个,就可以少枚举一些,时间就节省一些。
举个例子,当left-height < right-height时(以下简称h-left, h-right),将左指针向右移动其实是抛弃了当前这个左指针作为左边界的可能性。为什么?因为当前这个左指针再作为左边界也不可能找到面积更大的情况。证明如下:
如果我们继续将这个左指针当作左边界,将右指针左移。那么会出现这样的状况:
- 左右边界距离right-left肯定变小;
- min(h-left,h-right)肯定也变不大,为什么?有两种情况:
① 如果移动后的h-right1 比原来的的h-right小,则移动后的min(h-left,h-right1) <= min(h-left,h-right)
② 如果移动后的h-right1 比原来的的h-right大或相等,则移动后的min(h-left,h-right1) = min(h-left,h-right) = h-left
那么结论就是left-height < right-height时,将左指针当作边界,右指针左移的所有情况的盛水面积min(h-left,h-right)*(right-left)都不可能大于当前这个面积,所以可以将当前这个左指针抛弃,将左指针右移寻找面积更大的情况。
同样,右指针高度更小时,左指针右移同理。
java实现
class Solution {
public int maxArea(int[] height) {
//双指针
int n = height.length;
int res = 0;
int left = 0;//左指针初始位置
int right = n-1;//右指针初始位置
while(left < right){
res = Math.max(res,Math.min(height[left],height[right])*(right-left));
if(height[left]<= height[right]){
left++;
}else{
right--;
}
}
return res;
}
}
优化
主要优化的是,如果移动后的指针比原始指针还小的话,就继续移动,找比原来更高的,又抛弃了一些不可能的选择不用进行无用的计算和比较面积操作。
class Solution {
public int maxArea(int[] height) {
//双指针优化
int res = 0;
int left = 0;//左指针初始位置
int right = height.length-1;//右指针初始位置
int lmax = height[left], rmax = height[right];//维护左指针的高度和右指针的高度
while(left < right){
res = Math.max(res,Math.min(height[left],height[right])*(right-left));
if(lmax <= rmax){
while(left < right && height[left] <= lmax){//移动后的左指针高度比之前还小就继续移动 注意“小于等于”
++left;
}
lmax = height[left];
}else{
while(left < right && height[right] <= rmax){//移动后的右指针高度比之前还小就继续移动
--right;
}
rmax = height[right];
}
}
return res;
}
}
参考: 力扣官方题解