盛最多水的容器

304 阅读1分钟

「这是我参与2022首次更文挑战的第33天,活动详情查看:2022首次更文挑战

前言

笔者除了大学时期选修过《算法设计与分析》和《数据结构》还是浑浑噩噩度过的(当时觉得和编程没多大关系),其他时间对算法接触也比较少,但是随着开发时间变长对一些底层代码/处理机制有所接触越发觉得算法的重要性,所以决定开始系统的学习(主要是刷力扣上的题目)和整理,也希望还没开始学习的人尽早开始。

系列文章收录《算法》专栏中。

力扣题目链接

问题描述

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

说明:你不能倾斜容器。

示例 1:

image.png

输入:[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

提示:

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

剖析

我们先确定下怎么计算容器容水量,设现在height[i]和height[j]两条线和x轴组成的容器可以存放更多的水,p为容水量,那么p=|i-j|*min(height[i],height[j])。我们观察一下,在|i-j|能够保证最大的情况下,min(height[i],height[j])我们不断挪动把“较小值”变大尝试找到p最大的。如果在“把较小值变大”的过程中只要遇到比“较小值”大1的挪动都是有效的至少p没有变小,所以我们按照这个思路去进行挪动,下面描述下算法步骤:

  1. 左右两个指针指向最小下标i和最大小标j,计算p。
  2. 如果height[i]小左指针就往右移动,如果height[j]小右指针就往左移动,移动一下计算一下面积,返回最大面积。

时间复杂度为O(n),空间复杂度为O(1)。

注意:此算法实现步骤比较简单,注意++i和i++的区别,尽量使用前置,后面会出一篇字节码相关文章从字节码的角度来进行解释。

代码

public class MaxArea {
    /**
     * 我们先确定下怎么计算容器容水量,设现在height[i]和height[j]两条线和x轴组成的容器可以存放更多的水,p为容水量,那么p=|i-j|*min(height[i],height[j])。我们观察一下,在|i-j|能够保证最大的情况下,min(height[i],height[j])我们不断挪动把“较小值”变大尝试找到p最大的。如果在“把较小值变大”的过程中只要遇到比“较小值”大1的挪动都是有效的至少p没有变小,所以我们按照这个思路去进行挪动,下面描述下算法步骤:
     * 1. 左右两个指针指向最小下标i和最大小标j,计算p。
     * 2. 如果height[i]小左指针就往右移动,如果height[j]小右指针就往左移动,移动一下计算一下面积,返回最大面积。
     * <p>
     * 时间复杂度为O(n),空间复杂度为O(1)。
     *
     * @param height
     * @return
     */
    public static int maxArea(int[] height) {
        int p = 0;
        int i = 0;
        int j = height.length - 1;
        while (i < j) {
            int pTemp = (j - i) * Math.min(height[i], height[j]);
            if (height[i] <= height[j]) {
                ++i;
            } else {
                --j;
            }
            p = Math.max(pTemp, p);
        }
        return p;
    }

    public static void main(String[] args) {
        System.out.println(maxArea(new int[]{1, 8, 6, 2, 5, 4, 8, 3, 7}));
    }
}