Python·算法·每日一题(3月1日)盛最多水的容器

题目

  • 给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
  • 找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
  • 返回容器可以储存的最大水量。
  • 说明:你不能倾斜容器。

示例

  • 示例一

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

  • 示例二

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


提示

  • n == height.length
  • 2 <= n <= 1 0 5 10^5 105
  • 0 <= height[i] <= 1 0 4 10^4 104

思路及算法代码

说明

本题是一道经典的面试题,最优的做法是使用「双指针」。

分析

我们先从示例开始分析,一步步地解释双指针算法的过程。

题目中的示例为:

[1, 8, 6, 2, 5, 4, 8, 3, 7]
 ^                       ^

在初始时,左右指针分别指向数组的左右两端,它们可以容纳的水量为 min(1,7)∗8=8

此时我们需要移动一个指针。移动哪一个呢?直觉告诉我们,应该移动对应数字较小的那个指针(即此时的左指针)。这是因为,由于容纳的水量是由两个指针指向的数字中较小值∗指针之间的距离决定的。如果我们移动数字较大的那个指针,那么前者「两个指针指向的数字中较小值」不会增加,后者「指针之间的距离」会减小,那么这个乘积会减小。因此,我们移动数字较大的那个指针是不合理的。因此,我们移动 数字较小的那个指针。

所以,我们将左指针向右移动:

[1, 8, 6, 2, 5, 4, 8, 3, 7]
    ^                    ^

此时可以容纳的水量为 min(8,7)∗7=49。由于右指针对应的数字较小,我们移动右指针:

[1, 8, 6, 2, 5, 4, 8, 3, 7]
    ^                 ^

此时可以容纳的水量为 min(8,3)∗6=18。由于右指针对应的数字较小,我们移动右指针:

[1, 8, 6, 2, 5, 4, 8, 3, 7]
    ^              ^

此时可以容纳的水量为 min(8,8)∗5=40。两指针对应的数字相同,我们可以任意移动一个,例如左指针:

[1, 8, 6, 2, 5, 4, 8, 3, 7]
       ^           ^

此时可以容纳的水量为 min(6,8)∗4=24。由于左指针对应的数字较小,我们移动左指针,并且可以发现,在这之后左指针对应的数字总是较小,因此我们会一直移动左指针,直到两个指针重合。在这期间,对应的可以容纳的水量为:min(2,8)∗3=6min(5,8)∗2=10min(4,8)∗1=4

在我们移动指针的过程中,计算到的最多可以容纳的数量为 49,即为最终的答案。

代码

python 复制代码
class Solution:
    def maxArea(self, height: List[int]) -> int:
        # 初始化两个指针,一个指向列表开头,另一个指向列表末尾。
        l, r = 0, len(height) - 1
        # 初始化变量以存储最大面积。
        ans = 0
        
        # 循环直到两个指针相遇。
        while l < r:
            # 计算两个指针之间的面积。
            area = min(height[l], height[r]) * (r - l)
            # 更新最大面积。
            ans = max(ans, area)
            
            # 将指向较小高度的指针向另一个指针移动。
            if height[l] <= height[r]:
                l += 1
            else:
                r -= 1
        
        # 返回找到的最大面积。
        return ans

复杂复分析

  • 时间复杂度:O(N),双指针总计最多遍历整个数组一次。
  • 空间复杂度:O(1),只需要额外的常数级别的空间。

知识点

  • 双指针代表了什么?
    双指针代表的是可以作为容器边界的所有位置的范围 。在一开始,双指针指向数组的左右边界,表示 数组中所有的位置都可以作为容器的边界 ,因为我们还没有进行过任何尝试。在这之后,我们每次将 对应的数字较小的那个指针另一个指针 的方向移动一个位置,就表示我们认为 这个指针不可能再作为容器的边界了
  • 为什么对应的数字较小的那个指针不可能再作为容器的边界了?
    • 在上面的分析部分,我们对这个问题有了一点初步的想法。这里我们定量地进行证明。
    • 考虑第一步 ,假设当前左指针和右指针指向的数分别为 x 和 y,不失一般性,我们假设 x≤y。同时,两个指针之间的距离为 t。那么,它们组成的容器的容量为:min(x,y)∗t=x∗t 。我们可以断定,如果我们保持左指针的位置不变,那么无论右指针在哪里,这个容器的容量都不会超过 x∗t 了 。注意这里右指针只能向左移动,因为 我们考虑的是第一步 ,也就是 指针还指向数组的左右边界的时候
    • 我们任意向左移动右指针,指向的数为 y 1 y_1 y1 ,两个指针之间的距离为 t 1 t_1 t1 ,那么显然有 t 1 t_1 t1 < t,并且 min⁡(x, y 1 y_1 y1)≤min⁡(x,y):
      • 如果 y 1 y_1 y1 ≤ y,那么 min⁡(x, y 1 y_1 y1) ≤ min⁡(x,y);
      • 如果 y 1 y_1 y1 > y,那么 min⁡(x, y 1 y_1 y1) > min⁡(x,y);
    • 因此有:min⁡(x, y t y_t yt)∗ t 1 t_1 t1<min⁡(x,y)∗t
    • 即无论我们怎么移动右指针,得到的容器的容量都小于移动前容器的容量。也就是说,这个左指针对应的数不会作为容器的边界了 ,那么我们就可以丢弃这个位置,将左指针向右移动一个位置,此时新的左指针于原先的右指针之间的左右位置,才可能会作为容器的边界。
    • 这样以来,我们将问题的规模减小了 1,被我们丢弃的那个位置就相当于消失了。此时的左右指针,就指向了一个新的、规模减少了的问题的数组的左右边界 ,因此,我们可以继续像之前 考虑第一步 那样考虑这个问题:
      • 求出当前双指针对应的容器的容量;
      • 对应数字较小的那个指针以后不可能作为容器的边界了,将其丢弃,并移动对应的指针。
  • 最后的答案是什么?
    答案就是我们每次以双指针为左右边界(也就是「数组」的左右边界)计算出的容量中的最大值。
相关推荐
qwe3526335 分钟前
自定义数据集使用scikit-learn中的包实现线性回归方法对其进行拟合
开发语言·python
人生无根蒂,飘如陌上尘23 分钟前
pycharm踩坑(1)
ide·python·pycharm
I"ll carry you1 小时前
【Django教程】用户管理系统
python·django
SharkWeek.2 小时前
【力扣Hot 100】普通数组2
数据结构·算法·leetcode
cuber膜拜2 小时前
jupyter使用 Token 认证登录
ide·python·jupyter
张登杰踩3 小时前
pytorch2.5实例教程
pytorch·python
codists3 小时前
《CPython Internals》阅读笔记:p353-p355
python
Change is good3 小时前
selenium定位元素的方法
python·xpath定位
Change is good3 小时前
selenium clear()方法清除文本框内容
python·selenium·测试工具
大懒猫软件8 小时前
如何运用python爬虫获取大型资讯类网站文章,并同时导出pdf或word格式文本?
python·深度学习·自然语言处理·网络爬虫