题目
给定 n 个正整数 a1,a2,…,an,其中每个点的坐标用(i, ai)表示。 画 n 条直线,使得线 i 的两个端点处于(i,ai)和(i,0)处。请找出其中的两条直线,使得他们与 X 轴形成的容器能够装最多的水。
注意: 你不能倾斜容器,n 至少是2。
分析
思路一:暴力双遍历
时间复杂度为 O(n^2)
思路二:双指针
减少循环的核心思路是省去没有必要的遍历,并且确保所需的答案都能够遍历到。
- 假设现在有一个容器,则容器的盛水量取决于容器的底和容器较短的那条高
- 从最大的底长入手,即当容器的底等于数组的长度时,则容器的盛水量为较短边的长乘底
- 可见只有较短边会对盛水量造成影响,因此移动较短边的指针,并比较当前盛水量和当前最大盛水量。直至左右指针相遇。
可以证明,如果移动较高的边,则盛水量只会变少;移动较低的边,则可以遍历到最大的情况。
证明
假设:该算法并没有遍历到容量最大的情况
- 我们令容量最大时的指针为p_left和p_right。根据题设,我们可以假设遍历时左指针先到达p_left,但是当左指针为p_left时,右指针还没有经过p_right左指针就移动了
- 已知当左指针停留在p_left时,它只有在两种场景下会发生改变
- 左指针和右指针在p_left相遇,则右指针一定在前往p_left的途中经过p_right,与题设矛盾
- 右指针位于p_right右侧且当前的值大于左指针。则在这种情况下,此时容器的盛水量比题设中最大的盛水量还要大,与题设矛盾
因此该算法的遍历一定经过了最大的盛水量的情况
解题
class Solution {
public int maxArea(int[] height) {
int left = 0;
int right = height.length-1;
int max = 0;
while (left < right) {
max = Math.max(max, Math.min(height[left], height[right]) * (right - left));
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return max;
}
}
启示
- 最直接的方法,多次循环一定要谨慎,思考有没有更好的方法。
- 去除过多循环最好的方法就是双指针
- 双指针的方向通常有两种
- 都从左向右,根据期间值的大小决定哪个指针右移以及右移到什么位置
- 从两端到中间,距离越来越短,知道相遇
- 以上两种方法都需要保证遍历到了最大函数值。