题目
给定 nnn 个非负整数 a1,a2,…,ana_1,a_2,…,a_na1,a2,…,an,每个整数对应一个坐标为 (i,ai)(i, a_i)(i,ai) 的点。这样形成了 nnn 条竖直的线段,线段的两个端点分别为 (i,ai)(i, a_i)(i,ai) 和 (i,0)(i, 0)(i,0)。
要求找到两条线段当做两个挡板,使得他们与 x 轴组成的容器可以盛最多的水,并输出最大容量。
示例:
输入: [1,8,6,2,5,4,8,3,7]
输出: 49
解释: 在第2根线(8)与最后一根线(7)之间的容量最大,容量为 min(8,7)∗7=49min(8, 7) * 7 = 49min(8,7)∗7=49。
思路
1、暴力算法
最容易想到的算法为暴力算法,即编写一个双重循环,遍历所有不同两根线段,计算线段间的容量,选出最大容量。
这时候算法复杂度为 O(n2)O(n^2)O(n2)。
2、逼近法
定义两个指针,分别指向最左边的线段和最右边的线段,然后逐渐将两个指针往中间移动,每次移动后计算一边当前的容量,然后选择其中最大的容量。但是这样做不会遍历所有不同的两根线段,如何保证一定会遍历到最大容量的两根线段呢?
策略是,每次移动时,将左右指针的两根线段中的较短的线段,往中间移动一位。
为什么要这样做?因为容器的容量,是由两根线段中较短的那根决定的,改变短线段可以改变容量。
严格的证明:

图示是很多线段中的一部分,不失一般性地,比如某一次迭代之后,左右指针已经移动到了 lll 和 rrr 的位置(即蓝色轮廓),下一步该选择是 lll 向右移动一位,还是 rrr 向左移动一位。
假如我们最后的答案是 aaa 和 bbb 之间的容量是最大的(即红色线段),其中 bbb 和 rrr 是同一个线段。如果 lll 能一直向右移动,直至和 aaa 重合,那么说明算法成功,找到了最大容量的 ababab 线段。
那么有没有可能出现 rrr 向左移动一位,导致 rrr 位于 aaa 和 bbb 之间呢?如果这样,那么之后无论再怎么移动,都不可能找到最优解 ababab 了,算法也就失败了。
结论是:rrr 不可能位于 aaa 和 bbb 之间。
可以用反证法证明:
假如下一步是 rrr 向左移动一位,那么根据移动策略,现在必然有 lll 的高度大于 rrr 的高度,才会使得 rrr 左移。这样,一定会有 lll 和 bbb 组成的容量大于 aaa 和 bbb 组成的容量,因为:
V(l,b)=(b−l)∗min(l,b)=(b−l)∗h(b)V(l,b)=(b-l)*min(l,b)=(b-l)*h(b)V(l,b)=(b−l)∗min(l,b)=(b−l)∗h(b)
V(a,b)=(b−a)∗min(a,b)≤(b−a)∗h(b)V(a,b)=(b-a)*min(a,b)\le (b-a)*h(b)V(a,b)=(b−a)∗min(a,b)≤(b−a)∗h(b)
而 lll 在 aaa 左边,距离 bbb 的距离更远,因此有:
V(a,b)<V(l,b)V(a,b)<V(l,b)V(a,b)<V(l,b)
其中,VVV 表示容量,hhh 表示高度。
这和 ababab 是最大容量相矛盾。因此,逼近法是有效的,肯定会找到正确答案。
逼近法的时间复杂度为 O(n)O(n)O(n)。
python实现
def maxArea(height):
"""
:type height: List[int]
:rtype: int
从两边往中间寻找。
"""
max_area = 0
l = 0
r = len(height) - 1
while(l < r):
max_area = max(max_area, min(height[l], height[r]) * (r - l))
if height[l] < height[r]:
l += 1
else:
r -= 1
return max_area
if '__main__' == __name__:
height = [1,8,6,2,5,4,8,3,7]
print(maxArea(height))

本文探讨了给定一系列垂直线段,如何找出其中两条作为边界,使得这两条线段与x轴围成的容器能够盛放最多的水。通过对比暴力算法和逼近法,详细解析了逼近法的原理和有效性,最终给出了Python实现代码。
977

被折叠的 条评论
为什么被折叠?



