题目
给定 n n n 个非负整数 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,…,an,每个整数对应一个坐标为 ( i , a i ) (i, a_i) (i,ai) 的点。这样形成了 n n n 条竖直的线段,线段的两个端点分别为 ( i , a i ) (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)之间的容量最大,容量为
m
i
n
(
8
,
7
)
∗
7
=
49
min(8, 7) * 7 = 49
min(8,7)∗7=49。
思路
1、暴力算法
最容易想到的算法为暴力算法,即编写一个双重循环,遍历所有不同两根线段,计算线段间的容量,选出最大容量。
这时候算法复杂度为 O ( n 2 ) O(n^2) O(n2)。
2、逼近法
定义两个指针,分别指向最左边的线段和最右边的线段,然后逐渐将两个指针往中间移动,每次移动后计算一边当前的容量,然后选择其中最大的容量。但是这样做不会遍历所有不同的两根线段,如何保证一定会遍历到最大容量的两根线段呢?
策略是,每次移动时,将左右指针的两根线段中的较短的线段,往中间移动一位。
为什么要这样做?因为容器的容量,是由两根线段中较短的那根决定的,改变短线段可以改变容量。
严格的证明:
图示是很多线段中的一部分,不失一般性地,比如某一次迭代之后,左右指针已经移动到了
l
l
l 和
r
r
r 的位置(即蓝色轮廓),下一步该选择是
l
l
l 向右移动一位,还是
r
r
r 向左移动一位。
假如我们最后的答案是 a a a 和 b b b 之间的容量是最大的(即红色线段),其中 b b b 和 r r r 是同一个线段。如果 l l l 能一直向右移动,直至和 a a a 重合,那么说明算法成功,找到了最大容量的 a b ab ab 线段。
那么有没有可能出现 r r r 向左移动一位,导致 r r r 位于 a a a 和 b b b 之间呢?如果这样,那么之后无论再怎么移动,都不可能找到最优解 a b ab ab 了,算法也就失败了。
结论是: r r r 不可能位于 a a a 和 b b b 之间。
可以用反证法证明:
假如下一步是 r r r 向左移动一位,那么根据移动策略,现在必然有 l l l 的高度大于 r r r 的高度,才会使得 r r r 左移。这样,一定会有 l l l 和 b b b 组成的容量大于 a a a 和 b b b 组成的容量,因为:
V
(
l
,
b
)
=
(
b
−
l
)
∗
m
i
n
(
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
)
∗
m
i
n
(
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)
而 l l l 在 a a a 左边,距离 b b b 的距离更远,因此有:
V ( a , b ) < V ( l , b ) V(a,b)<V(l,b) V(a,b)<V(l,b)
其中, V V V 表示容量, h h h 表示高度。
这和 a b ab ab 是最大容量相矛盾。因此,逼近法是有效的,肯定会找到正确答案。
逼近法的时间复杂度为 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))