LeetCode_11 盛最多水的容器(Container With Most Water) (暴力+)双指针

原题

给定n个非负整数a1,a2,…,an,每个数代表点(i,ai)。坐标系上有n条垂直于x轴的线段,每条线段的两个端点分别是(i,ai)和(i,0)。找到其中的两条线段,使它们和x轴组成的容器的容积最大。

注意:容器不能倾斜,n至少为2.

来源:LeetCode
链接:https://leetcode.com/problems/container-with-most-water/
 
 

解析

考虑这道题的时候,最直接的思路就是暴力,把所有可能的容积都求出来,保留最大值。但这样做的时间复杂度是O(n2),显然不是最理想。
 
解法一:暴力

代码:

class Solution {
public:
    int maxArea(vector<int>& height) {
        int maxA = 0;
        for(int i = 0; i < height.size(); ++i){
            int l = height[i];
            for(int j = i+1; j < height.size(); ++j){
                int r = height[j];
                int tmp = (j-i)*(l<=r? l : r);
                if(tmp > maxA)
                    maxA = tmp;
            }
        }
        return maxA;
    }
};

运行情况如下:
Runtime: 728 ms
Memory Usage: 9.8 MB

然后考虑过动态规划,但是会发现根本没办法界定合适的边界,各个子问题之间也没有依赖关系。因为计算“容积”需要考虑的不仅是两条边的高度大小,还有底边的长度,这是动态规划没有办法通过简单的边界条件表示出来的。

 
解法二:双指针

双指针在这种与线性序列相关的问题中常被使用,尤其是链表。在这道题中,首尾各有一个指针代表目前考察的容器边界,时时更新目前最大容积值。那么,指针应该怎么移动呢?

其中有一个规律,由于每个容器的“容积”只取决于较短的边和底边的长度,那么,对于当前的边界ai和aj,不妨设height[ai]<height[aj],当前的容积x*height[ai](假设x是底边长度),此时不管移动左指针还是右指针(都向内移动,解释见下),x都必然减小,所以现在要做的就是使得移动后,较短的边可能会比height[ai]长一点。所以,如果移动右指针,较短的边依旧<=height[ai],而如果移动左指针,较短的边有可能会比height[ai]长。
因此,每次计算好当前容器的容积之后,就将较短边一方的指针向内移动。

(为什么指针不会再向外移动呢(即左指针向左,右指针向右)?以左指针为例,因为它此前必然是由于指向的边较短才向右移动(而在移动之前已经得到了当时容器的容积x*h,设为V),所以无论现在右指针移动了多少,如果再把左指针向左移回去,首先,底边长度必然<=x,而较短边的高度也是<=h,那么得到的容积一定<=V,可见对于找更优解是没有帮助的。)

显然,这样做的时间复杂度为O(n)。顺便提一句,由于解题必须至少要将height全部读一遍,因此问题的计算复杂度的下界就为O(n),也即双指针法是解这道题的最好的算法。

代码如下:

class Solution {
public:
    int maxArea(vector<int>& height) {
        int lp = 0; //左指针
        int rp = height.size()-1; //右指针
        int maxA = 0;
        while(lp < rp){
            int lh = height[lp];
            int rh = height[rp];
            bool flag = lh <= rh;
            int tmp = (rp-lp)*(flag? lh : rh);
            if(tmp > maxA)
                maxA = tmp;
            if(flag)
                lp++;
            else
                rp--;
        }
        return maxA;
    }
};

运行情况:
Runtime: 16 ms
Memory Usage: 9.7 MB

这道题的关键在于要能想到使用双指针,且能发现其中的规律,一旦确定了算法,代码写起来其实很简单。

如有错误及不足,欢迎交流指正~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值