题目描述
给定 $n$ 个非负整数 $a_1, a_2, … a_n$,表示平面上有 $n$ 条竖线,第 $i$ 条竖线的两个端点是 $(i, a_i)$ 和 $(i, 0)$。请找出两条竖线,使得它们与 $x$ 轴组成的容器能盛最多的水。
注意:不可以把线倾斜,并且 $n \ge 2$。
算法
(指针扫描) $O(n)$
这题的思路比较精巧,我们先给出做法,再给出证明。
做法:用两个指针 $i, j$ 分别指向首尾,如果 $a_i \gt a_j$,则 $j\-\-$;否则 $i++$,直到 $i =j$ 为止,每次迭代更新最大值。
证明:假设最优解对应的两条线的下标是 $i’, j’(i’ \lt j’)$,在 $i, j$ 不断靠近的过程中,不妨假设 $i$ 先走到 $i’$,则此时有 $j’ \lt j$。反证,如果此时 $a_i \le a_j$,设 $S$ 表示 $i, j能盛多少水$,$S’$ 表示 $i’, j’$ 能盛多少水,则:
$S = min(a_i, a_j) * (j - i)$
$ = a_i * (j - i) $
$ \gt a_i * (j’ - i)$
$ \ge min(a_i, a_{j’}) * (j’ - i) = S’$
与 $S’$ 是最优解矛盾,因此 $a_i \gt a_j$,所以 $j$ 会一直走到 $j’$,从而得到最优解。
时间复杂度分析:两个指针总共扫描 $n$ 次,因此总时间复杂度是 $O(n)$.
C++ 代码
class Solution {
public:
int maxArea(vector<int>& height) {
int res = 0;
for (int i = 0, j = height.size() - 1; i < j; )
{
res = max(res,
min(height[i], height[j]) * (j - i));
if (height[i] > height[j]) j -- ;
else i ++ ;
}
return res;
}
};
双指针,如果nums[i] <= nums[j],那么(i, j) , (i, j - 1), (i, j - 2), .... (i, i + 1)这些范围相当于都考虑过了,并且area就是(i,j)时的面积,所以 i 已经没必要存在了,我们让 i++
一语惊醒梦中人
orz
#### 为之后臭名昭著的”接雨水”埋下伏笔
y总最后的为啥是min(ai,aj′)∗(j′−i)=S’?不是min(ai’, aj’)*(j’-i’)=S’吗?
“ 不妨设i先走到i’ ”,此时i和i’是一样的。是先固定下左边的指针再去考虑右边的性质吧,如果两个指针都在动应该很难想到做法(也可能是因为我菜orz)
对滴。
Mark 下我能看得懂的证明~
好滴~
没有看懂你在证明什么,还麻烦能不能在详细解释一下?另外还有个疑问,就是如果两个指针h[i]==h[j]的时候,应该怎么走?它有几种情况i++,j 或者是i,j–,这种情况烦请解释一下。
在证明上述做法一定可以得到最优解。从上述证明中可以看出,如果
h[i] == h[j]
,那么移动哪个指针都是可以的。ps:上面的证明写得相当清楚啊。。在数学上一般都是这么证明的,只是抽象符号比较多,同学你要慢慢习惯啊。