一、题目
给定 n 个非负整数
a
1
,
a
2
,
.
.
.
,
a
n
a1,a2,...,an
a1,a2,...,an,每个数代表坐标中的一个点
(
i
,
a
i
)
(i, ai)
(i,ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为
(
i
,
a
i
)
(i, ai)
(i,ai) 和
(
i
,
0
)
(i, 0)
(i,0)。 找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
二、分析
方法一:暴力枚举
简单地考虑每对可能出现的线段组合并找出这些情况之下的最大面积。
算法
双指针 i, j 分别位数组开始,根据规则移动指针,按条件查找出每一种结果,将其与前一次结果相比。
水槽的实际高度由两板中的短板决定,可得公式, S ( i , j ) = m i n ( h [ i ] , h [ j ] ) × ( j − i ) S(i,j) = min (h[i],h[j]) × (j-i) S(i,j)=min(h[i],h[j])×(j−i)。
public int maxArea(int[] height) {
int maxArea = 0;
for (int i = 0; i < height.length; i++)
for (int j = i + 1; j < height.length; j++) {
int minHeight = height[i] < height[j] ? height[i] : height[j];
maxArea = Math.max(maxArea, minHeight * (j - i));
}
return maxArea;
}
复杂度分析
- 时间复杂度: O ( n 2 ) O(n^2) O(n2), 计算所有 n ( n − 1 ) 2 \cfrac{n(n−1)}{2} 2n(n−1) 种高度组合的面积。
- 空间复杂度: O ( 1 ) O(1) O(1)
方法二:双指针
想法
矩阵的面积与两个因素有关:
- 矩阵的长度:两条垂直线的距离
- 矩阵的宽度:两条垂直线其中较短一条的长度
由于面积取决于边长短的那一端,假设为 m m m,所以要想得到比当前更大的面积,边长短的那一端必须舍弃,因为如果不舍弃,高最大就是 m m m,而随着指针的移动宽会一直减小,因此面积只会越来越小。
因此,要矩阵面积最大化, 两条垂直线的距离越远越好,两条垂直线的最短长度也要越长越好。
算法
设置双指针 i, j 分别位于数组两端,根据规则移动指针(后续说明),并且更新面积最大值 res,直到 i == j 时返回 res。
·
public int maxArea(int[] height) {
int maxArea = 0;
int l = 0, r = height.length - 1;
while (l < r) {
maxArea = height[l] < height[r] ?
Math.max(maxArea, (r - l) * height[l++]):
Math.max(maxArea, (r - l) * height[r--]);
}
return maxArea;
}
指针移动规则与证明:
证法一:
由于水槽的实际高度由两板中的短板决定,则可得面积公式 S(i, j) = min (h[i], h[j]) × (j - i)。
在每一个状态下,无论长板或短板收窄 1 格,都会导致水槽 width -1:
- 若向内移动短板,水槽的短板 min(h[i], h[j]) 可能变大,因此水槽面积 S(i, j) 可能增大。
- 若向内移动长板,水槽的短板 min(h[i], h[j]) 不变或变小,下个水槽的面积一定小于当前水槽面积。
- 因此,向内收窄短板可以获取面积最大值
换个角度理解:
若不指定移动规则,所有移动出现的 S ( i , j ) S(i, j) S(i,j) 的状态数为 C n 2 C_n^2 Cn2,即暴力枚举出所有状态。
在状态 S(i, j)下向内移动短板至 S ( i + 1 , j ) S(i+1, j) S(i+1,j)(假设 h [ i ] < h [ j ] h[i] < h[j] h[i]<h[j] ),则相当于消去了 S ( i , j − 1 ) , S ( i , j − 2 ) , . . . , S ( i , i + 1 ) {S(i, j-1), S(i, j-2), ... , S(i, i+1)} S(i,j−1),S(i,j−2),...,S(i,i+1) 状态集合。
而所有消去状态的面积一定 < = S ( i , j ) <= S(i, j) <=S(i,j):
- 短板高度:相比 S(i, j) 相同或更短(<= h[i]);
- 底边宽度:相比 S(i, j) 更短。
因此所有消去的状态的面积都 < S(i, j)。通俗的讲,我们每次向内移动短板,所有的消去状态都不会导致丢失 Max(S) 。
证法二:
简单反证法证明:通过双指针方法,两个指针一定会同时经过最大面积对应的指针位置。
|
| |
| |
......|......|......
———————————————————————————————————————
m n
如图,若 m , n m,n m,n 之间的面积为最大面积。
双指针方法的规律是:每次都会向内移动偏矮的指针(可观察案例)。要证明两个指针一定会移动到 m 和 n 位置,只需证明:
m 左侧的指针点等于或矮于 n,n右侧指针等于或矮于 m。
假设 m 左侧有一个点 p,高度高于 n.
|
| |
| | |
| | |
...|...|......|......
———————————————————————————————————————
p m n
因为:
AreaMN = ( n - m ) * min( arr[ m ], arr[ n ] )
AreaPN = ( n - p ) * min( arr[ p ], arr[ n ] )
又:
( n - m ) <= ( n - p )
min( arr[ m ], arr[ n ] ) <= min( arr[ p ], arr[ n ] )
所以: A r e a M N < A r e a P N AreaMN < AreaPN AreaMN<AreaPN, 与 m 和 n 构成最大面积相矛盾,所以假设不成立,即 m 左侧的点都不高于 n,即等于或矮于 n。同理可证,n 右侧指针等于或矮于 m。所以通过双指针方法,两个指针一定会同时经过最大面积对应的指针位置。
复杂度分析
- 时间复杂度 O ( N ) O(N) O(N): 双指针遍历一次底边宽度 N。
- 空间复杂度 O ( 1 ) O(1) O(1): 指针使用常数额外空间。