[LeetCode]#4盛最多水的容器:双指针法及其证明

问题

给定 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器,且 n 的值至少为 2。
[LeetCode]#4盛最多水的容器:双指针法及其证明_第1张图片

图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

示例:

输入: [1,8,6,2,5,4,8,3,7]
输出: 49

算法

我们在数组中使用两个指针,一个放在开始,一个置于末尾。在每一步中,我们将指向较短线段的指针向较长的线段那端移动;同时,我们记录下所有步骤里最大的面积: maxSquare

证明

我们将所有情况用一棵递归树列出,如下:

m , n 表示前后指针,H[m] 表示位置 m 处的高度,n 是输入的数据长度。S(m,n) = min(H[m],H[n]) * (n-m)(m,n) 对的面积。h[m,n] 表示当前位置的树高, h = n-m

红色圈出部分为双指针法可能的一条路径(路径并不唯一)。

这意味着:在双指针路径的红圈外任意一个情况的 S(m,n) 均小于 maxSquare

下面我们来证明是否一定如此。

反证法

假设,存在一个双指针路径之外的 S(m,n) > maxSquare。则:
S ( m , n )    =    { H ( m ) ∗ h      H ( m ) < H ( n ) H ( n ) ∗ h      H ( n ) < H ( m ) S\left(m,n\right)\;=\;\left\{\begin{array}{l}H(m)\ast h\;\;H(m)S(m,n)={H(m)hH(m)<H(n)H(n)hH(n)<H(m)
我们观察递归树可以发现,当 (m,n) 在双指针路径的左边时,在路径上必定存在 (m,X) (可能会有多个),且 (m,X)(m,n) 的上方;同理,当 (m,n) 在双指针路径的右边时,在路径上也必定存在 (X,n) ,且 (X,n)(m,n) 的上方。

(1) 对于 (m,n) 在路径左边的情况

根据观察,由于路径上存在 (m,X) ,且 (m,X)(m,n) 的上方,即:h[m,X]>h[m,n]

由于若对任意一个 (m,X)H(X) 恒成立的话,(m,n) 将不会在路径的左边,而是在路径中。故在路径上必定存在一个且仅且一个 (m,α) 满足: H(α)>H(m)S(m,α) = H(m) * h[m,α]

由于h[m,α]>h[m,n] ,而若 H(m),这与我们的假设 S(m,n) > maxSquare 不符合。

所以我们可以得到:H(α)>H(m)>H(n)S(m,n) = H(n) * h(m,n) 。而 h[m,α]>h[m,n],即:S(m,α)>S(m,n),仍然与我们的假设 S(m,n) > maxSquare 不符合。故假设不成立!

(2) 对于 (m,n) 在路径右边的情况

同理就略了…

由此,我们可以确定双指针方法是正确的。代码如下:

/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function(height) {
    let i = 0, j = height.length-1;
    let square, max = 0;
    while(j-i >= 1){
        if(height[i]>height[j]){
            square = height[j] * (j-i);
            j--;
        }else{
            square = height[i] * (j-i);
            i++;
        }
        max = Math.max(square,max);
    }
    return max;
};

你可能感兴趣的:(LeetCode)