装最多水的容器 - 题解

1. 问题描述

  原题的地址见:Container With Most Water - LeetCode. 此问题等价于如下问题:

    给定所有元素非负的数组[a0, a1, ..., an-1], 计算(j - i) * min(ai, aj)(其中j > i)的最小值。

 

2. 暴力解法

  有了问题的描述,很容易写出暴力求解的算法:

int maxArea(vector<int>& height) {
    int maxVolumn = 0;
    for (int i = 0; i < height.size(); ++i) {
        for (int j = i + 1; j < height.size(); ++j) {
            int currentVolumn = (j - i) * min(height[j], height[i]);
            if (currentVolumn > maxVolumn) {
                maxVolumn = currentVolumn;
            }
        }
    }
    return maxVolumn;
}

  即:计算所有的(j - i) * min(aj, ai), 找出最大的值,时间复杂度为O(n2).

 

3. 更优的解法

  对于这个问题,不需要遍历每个(ai, aj)对。解法如下:

  假设输入为[a0, a1, ..., an-1],

    (a.) 设置两个指针i, j分别指向数组两端的元素, 即i = 0, j = n - 1. 置初始时的最大体积maxV = 0.

    (b.) 如果i < j, 计算当前的容器体积:v = (j - i) *  min(aj, ai), 置maxV = max(maxV, v); 否则(即i≥j时)返回maxV.

    (c.) 如果ai < aj, 则i = i + 1, 否则j = j - 1, 跳转到(b.).

  大概意思就是,考虑两端的木板:a0和an-1, 选择两个木板中较短的那个更新。也就是说,如果a0更短,那么就把a0更新为a1; 如果an-1更短, 就把an-1更新为an-2. 之后重复这个过程。

  这种解法更为有效,因为仅遍历了一次数组,其时间复杂度为O(n). 现在的关键问题是,它为什么是正确的?

  其实道理很简单,考虑第一次进入循环的情况,即i = 0, j = n - 1, 不妨设a0 < an-1. 这样,在第一次循环后,i = 0 + 1 = 1. 也就是说,我们淘汰了情况(a0, a1), (a0, a2), ..., (a0, an-2). 这些对为什么可以淘汰呢?

  原因在于,对于任意的对(a0, ak)(k = 1, 2, ..., n-2):

    如果ak ≤ a0, 那么(a0, ak)构成容器的体积为(k - 0) * ak, 显然小于(a0, an-1)构成容器的体积(n - 1 - 0) * a0.

    如果ak > a0, 那么(a0, ak)构成容器的体积为(k - 0) * a0, 这仍然小于(a0, an-1)构成容器的体积.

  因此,对于a0 < an-1的情况, (a0, an-1)构成的容器是有a0参与构成的容器中体积最大的那一个。因此,每次迭代时,我们只需要更新ai, aj中较小的那个所对应的下标即可。从这个问题中可以看出一种短板效应

  具体的C++实现如下:

int maxArea(vector<int>& height) {
    int i = 0, j = height.size() - 1;
    int maxVolumn = 0; 
        
    while (i < j) {
        if (height[i] < height[j]) {
            maxVolumn = max(maxVolumn, height[i] * (j - i));
            ++i;
        }
        else {
            maxVolumn = max(maxVolumn, height[j] * (j - i));
            --j;
        }
    }

    return maxVolumn;
}

 

posted @ 2023-05-08 12:59  overxus  阅读(23)  评论(0编辑  收藏  举报