【LeetCode】11-Container With Most Water 盛最多的水(双指针法、缩减搜索空间思想)

一、题目:盛最多的水

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

  说明:你不能倾斜容器。

  示例 1:

在这里插入图片描述

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

  示例 2:

输入:height = [1,1]
输出:1

  示例 3:

输入:height = [4,3,2,1,4]
输出:16

  示例 4:

输入:height = [1,2,1]
输出:2

  提示:

  • n = height.length
  • 2 <= n <= 3 * 104
  • 0 <= height[i] <= 3 * 104

二、解决方案

  本题使用双指针法进行解答。设计两个指针 leftright 分别指针容器的两端,即指针 left = 0right = height.length - 1。此时,容器的容量可通过如下公式进行计算:

maxContainer = (两个指针之间的距离) * (两个指针指向的元素中的最小值)

  算法流程如下图所示:

在这里插入图片描述

三、双指针法移动规则(即每次移动高度较小的一边)有效性验证

  1. 设每一个状态下的容器容量为 S(left, right) (0 <= left < right <= height.length - 1),则 S(left, right) = (right - left) * Math.min(height[left], height[right])
  2. 无论是移动较短的一边还是较长的一边,都会使得 (right - left) 减小 1。因此,我们。只关心在移动过程中height[left]height[right] 是变小,还是变大,还是保持不变。
  3. 对于较短的一边,此时是它决定当前状态的容器容量。则它移动之后有三种可能:变小、不变,变大,对应的 S(left, right) 的变化有:变小、变小、变大。
  4. 对于较长的一边,它移动之后也有三种可能:变小、不变、变大,对应的 S(left, right) 的变化有:变小、变小、变小因为此时决定容器容量的不是它的高度,且 (right - left) 变小。

  综上所述,每次移动高度较小的一边,能够有机会得到更大的容器容量。

四、背后的原理-缩减搜索空间思想

  此题中,假设一共有 n 个柱子,编号为 0,1,2,...,n,其高度分别为 H0,H1,H2,...,Hn。移动过程中的柱子 ij 满足约束条件:ij 都是合法的柱子下标,即 0 <= i < j <= n。移动柱子的目标是为了得到一组 (i, j) 使得容器的容量最大。以 n = 8 为例,此时全部的搜索空间如下所示:

在这里插入图片描述
  由于受 ij 约束条件的限制,搜索空间是白色的倒三角部分。可以看到,搜索空间的大小是 O(n^2) 数量级。若用暴力解法求解,一次只检查一个单元格,那么时间复杂度一定是 O(n^2)。要想得到 O(n) 的解法,我们就需要能够一次排除多个单元格。接下来说明缩减搜索空间的思想:

  一开始,我们检查右上方单元格 (0,7),即考虑最左边的 0 号柱子和最右边的 7 号柱子,计算它们之间容器容量。然后我们比较一下两根柱子的高度,关注其中较短的一根。

在这里插入图片描述
  假设左边的 0 号柱子较短。则 0 号柱子的高度即为容器当前高度的上限。由于 7 号柱子是离 0 号柱子最远的,水的宽度也就最大。若换其他的柱子和 0 号柱子配对,水的宽度只会更小,高度也不会增加,容纳水的面积只会更小。所以 0 号柱子和 6,5,…,1 号柱子的配对都可以排除掉了,这就相当于 i=0 的情况全部被排除,只记录 (0,7) 这组结果。

  对应于双指针解法的代码,就是 left++;对应于搜索空间,就是削减了一行的搜索空间,如下图所示。

在这里插入图片描述
  排除掉了搜索空间中的一行之后,我们再看剩余的搜索空间,仍然是倒三角形状。我们检查右上方的单元格 (1,7),即考虑 1 号柱子和 7 号柱子,计算它们之间容纳水的面积。然后,比较两根柱子的高度。

在这里插入图片描述
  假设此时 7 号柱子较短。同理, 7 号柱子的高度即为容器当前高度的上限,若换其他的柱子和 7 号柱子配对,水的宽度变小,高度也不会增加,容纳水的面积只会更小,即 7 号柱子和 2,3,…,6 号柱子的配对都可以排除掉了。这相当于 j=7 的情况全部被排除,只记录了 (1,7) 这组结果。

  对应于双指针解法的代码,就是 right--;对应于搜索空间,就是削减了一列的搜索空间,如下图所示。

在这里插入图片描述
  综上所述,无论柱子 ij 哪根更短,我们都可以排除掉一行或者一列的搜索空间。经过 n 步以后,就能排除所有的搜索空间,检查完所有的可能性。搜索空间的减小过程如下所示。

在这里插入图片描述

五、程序实现过程

  Java 解法一:

public int maxArea(int[] height) {
    int left = 0;
    int right = height.length - 1;
    int maxContainer = Integer.MIN_VALUE;

    while (left < right) {
        int width = right - left;
        int h = Math.min(height[left], height[right]);
        
        // 计算容器的最大容量
        maxContainer = Math.max(maxContainer, width * h);
        
        // 移动高度较小的边
        if (height[left] < height[right]){
            left++;
            continue;
        }
        right--;
    }
    return maxContainer;
}

  Java 解法二:使用三目运算符 A ? B : C,此处有一点需要注意,必须先做 (right - left) 的计算,然后再进行高度的计算和移动,即 Math.min(height[left++], height[right])Math.min(height[left], height[right--]),这是移动(自增)操作会影响 (right - left) 的结果。

public int maxArea(int[] height) {
    int left = 0;
    int right = height.length - 1;
    int maxContainer = Integer.MIN_VALUE;

    while (left < right){
        maxContainer = height[left] < height[right] ?
            Math.max(maxContainer,(right - left) * Math.min(height[left++],height[right])) :
        	Math.max(maxContainer,(right - left) * Math.min(height[left],height[right--]));
    }
    
    return maxContainer;
}

  Java 解法三:相对于上面两种解法,对于移动前后高度相同的,不再进行容器容量的计算。

public int maxArea(int[] height) {
    int left = 0;
    int right = height.length - 1;
    int maxContainer = Integer.MIN_VALUE;

    while (left < right) {
        int width = right - left;
        int h = Math.min(height[left], height[right]);
        
        // 计算容器的最大容量
        maxContainer = Math.max(maxContainer, width * h);
        
        // 若下一轮 while 循环仍和 h 相等,则说明移动前后高度没有发生变化,此时继续移动,且不计算容器容量
        // 若 h == height[left],说明 left 是高度更小的边
        while (left < right && h == height[left]){
            left++;
        }

        // 若 h == height[right],说明 right 是高度更小的边
        while (left < right && h == height[right]){
            right--;
        }
    }
    return maxContainer;
}
参考资料

  缩减搜索空间思想

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值