LeetCode之HOT100--011 盛最多水的容器

580 阅读3分钟

「这是我参与11月更文挑战的7天,活动详情查看:2021最后一次更文挑战」。

前言

一直都计划学习数据结构与基本算法,但是平时都看一阵停一阵。现在决心坚持下去,我准备从LeetCode的HOT100开始,每天完成1~2道习题,希望通过这种方式养成持续学习的习惯。因为我是做iOS开发的,主要是用Objective-C语言,最近也在学习Swift,所以本系列的题解都将使用swift语言完成,本文更新的是LeetCode中HOT100的第7题011盛最多水的容器。

题目

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

question_11.jpg

输入:[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 <= 105
0 <= height[i] <= 104

分析

本题的意思类似我们常见的木桶原理,一个木桶所能装水的多少取决于最短的那根木板。本题中两个元素aiaiajaj之间所能盛的水的多少为 Sij=(ji)min(aiaj),假设i<jSij = (j - i) * min(ai,aj),假设i < j。本题是要求所有sijsij中的最大值。
显然,最常见的方法是暴力法(枚举法),一次求出每一个sijsij的值,然后获取最大值进行返回,该方法对应的复杂度应该是 O(n2)。该方法的解题思路类似冒泡法,就是一个双重循环,就不细说了。
本题的题解采用双指针法,是对上述的暴力解法的改进版本。基本思路是设两指针 i,ji , j,指向的水槽板高度分别为h[i],h[j] h[i] , h[j] ,此状态下水槽面积为 S(i,j)S(i,j) 。由于可容纳水的高度由两板中的 短板 决定,因此可得如下 面积公式 :S(i,j)=min(h[i],h[j])×(ji)S(i,j)=min(h[i],h[j])×(j−i)

1628780627-VtSmcP-Picture0.png 在每个状态下,无论长板或短板向中间收窄一格,都会导致水槽 底边宽度 −1, 变短:

  • 若向内 移动短板 ,水槽的短板 min(h[i],h[j])min(h[i],h[j]) 可能变大,因此下个水槽的面积 可能增大 。
  • 若向内 移动长板 ,水槽的短板 min(h[i],h[j])min(h[i],h[j]) 不变或变小,因此下个水槽的面积 一定变小 。 因此,初始化双指针分列水槽左右两端,循环每轮将短板向内移动一格,并更新面积最大值,直到两指针相遇时跳出;即可获得最大面积。

双指针法的基本流程如下

1、初始化:设定双指针 left , right 初始值分别指向水槽左右两端的下标;设定maxArea初始值为0,表示当前最大容量
2、计算当前值:计算当前有leftright下标对应的元素组成的容器的的容量,S(left,right) = min(h[left],h[right]) × (rightleft),并更新maxArea为当前最大值
3、指针移动:将当前leftright对应的较小值,向内移动到比原先值大的新值(如果h[left] <= h[right],则将left向右移动,直到新的left对应的值大于原先left对应的值,如果h[left] < h[right],则将right向左移动,直到新的right对应的值大于原先right对应的值)
4、循环:重复步骤2~步骤4,直至left >= right

该方法的复杂度为 O(n),相对暴力解法更简单。

题解

class KLLC011 { 
    
    func maxArea(_ height: [Int]) -> Int { 
        //初始化
        var left = 0, right = height.count - 1 
        var maxArea = 0 
        //循环结束条件为left >= right,则循环继续的提交为left < right
        while left < right { 
            //计算当前left right组成的容积
            let curH = min(height[left], height[right]) 
            let curArea = curH * (right - left) 
            //更新maxArea
            if maxArea < curArea { 
                maxArea = curArea 
            } 
            //根据left right对应值的大小进行向内移动
            if height[left] <= height[right] { 
                //h[left] <= h[right],则将left向右移动,直到新的left对应的值大于原先left对应的值
                repeat { 
                    left += 1 
                } while left < right && curH >= height[left] 
            } else { 
                //h[left] < h[right],则将right向左移动,直到新的right对应的值大于原先right对应的值
                repeat { 
                    right -= 1 
                } while left < right && curH >= height[right] 
            } 
        }
        //返回最大容积
        return maxArea 
    } 
}