Leetcode 11. 盛最多水的容器 从暴力优化到双指针,明明白白

从暴力优化到双指针,明明白白

这道题的最优解法是左右双指针法。双指针法的难点在于难于想到,难以证明。接下来将一步一步地从暴力解法优化到双指针法。证明也就很简单了。

暴力解法
找出每一种情况,求出盛水值,最大的就是答案。

  1. i指向左挡板,从第一块到遍历倒数第二块。
  2. j指向右挡板,从倒数第一块遍历到i后面那一块。
  3. res保存最大盛水值。
  4. 返回res
    代码:
//cpp
#include <iostream>
#include <vector>
using namespace std;

class Solution {
public:
    int maxArea(vector<int>& height) {
        if(height.size() <= 1) return 0;
        int res = 0;//保存结果
        for(int i = 0; i < height.size() - 1; i++)//以i为左挡板,从O开始
        {
            for(int j = height.size() - 1; j > i; j--)//以j为右挡板,从height.size() - 1开始
            {
                int L = j - i;//底边长度
                int H = min(height[i], height[j]);//对短的板子为高
                res = max(res, L * H);//取最大值
            }
        }
        return res;
    }
};

int main()
{
    int n;
    vector<int> height;
    cin >> n;
    while(n--)
    {
        int he;
        cin >> he;
        height.push_back(he);
    }
    Solution S;
    cout << S.maxArea(height);
}

S[l,r]表示以第l块板为左挡板,第r块板为右挡板的盛水值。S[l, r]就等于min(height[l], height[r]) * (r - l)
以输入[1,8,6,2,5,4,8,3,7]为例,共8块挡板,看看都计算了哪些值:穷举.jpg

解题超时
优化一:

  1. 开始时,l指向第0块板子,r指最后一块板子。S[l, r]=min(1, 7) * (8 - 0) = 8。

第一次.jpg

  1. 向内移动指向较长挡板的r指针,盛水面积不会变大。向内移动r指针的时候,盛水值S[l, r] = min(height[l], height[r]) * (r - l)。min(height[l], height[r]) 不会大于height[l],也就是不会大于7。(r - l)会随着r内移减小。所以向内移动r指针的时候,盛水值不可能变大。也就是S[0,8]肯定大于S[0,7],S[0,6],S[0,5],S[0,4],S[0,3],S[0,2],S[0,1]因此知道了以height[0]为左挡板的最大盛水值。以后计算就不用考虑height[0]了

第一次缩小.jpg

  1. 子问题就变成了:在[8,6,2,5,4,8,3,7]中求出最大盛水值,然后与刚才的求出的以height[0]为左挡板的最大盛水值比较大小,大的为答案。
    第二次.jpg

  2. 子问题求解时,用l指向指向第0块挡板,也就是height[1],r指最后一块挡板,也就是height[8]S[l, r]=min(8, 7) * (8 - 1) = 56。这个时候,如果向内移动指向较长挡板的l指针,盛水面积不会变大,因为向内移动l指针的时候,盛水值S[l, r] = min(height[l], height[r]) * (r - l)min(height[l], height[r]) 不会大于height[r],也就是不会大于7。(r - l)会随着l内移减小。所以向内移动l指针的时候,盛水值不可能变大。也就是S[1,8]肯定大于S[2,8],S[3,8],S[4,8],S[5,8],S[6,8],S[7,8],就知道了以height[8]为右挡板的最大盛水值。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tpUp17ta-1607612206835)(https://cdn.acwing.com/media/article/image/2020/12/08/55289_1d04d89839-第二次缩小.jpg)]

  3. 子问题可以再次缩小,就变成了:在[8,6,2,5,4,8,3]中求出最大盛水值,然后与刚才的求出的以height[0]为左挡板的最大盛水值,以height[8]为右挡板的最大盛水值比较大小,大的为答案。

  4. 以此类推,每次就能求出以最外侧两个挡板中,短的挡板为边界的最大值。然后再一次缩小问题。就不需要计算所有的情况了,只需要计算出每块挡板为边界的最大值,然后求出其中的最大值,就是答案。

  5. 这样下去,求解空间就变为了:
    缩小.jpg

代码

//cpp
#include <iostream>
#include <vector>
using namespace std;

class Solution {
public:
    int maxArea(vector<int>& height) {
        if(height.size() <= 1) return 0;
        int res = 0;//保存答案
        int l = 0, r = height.size() - 1;//开始时,l指向最左边的挡板,r指向最右边的挡板
        while(l < r)//如果l,r之间还有挡板
        {
            res = max(min(height[l], height[r]) * (r - l), res);//计算盛水值
            if(height[l] <= height [r])//谁小谁以后就不用再考虑 
                l++;
            else
                r--;
        }
        return res;
    }
};

int main()
{
    int n;
    vector<int> height;
    cin >> n;
    while(n--)
    {
        int he;
        cin >> he;
        height.push_back(he);
    }
    Solution S;
    cout << S.maxArea(height);
}

时间上,l,r指针遍历一遍,所以时间复杂度是O(n)。空间上,没有开辟与输入有关的空间,所以空间复杂度是O(1)。
觉得还不错,欢迎点赞、收藏评论~

欢迎关注公众号,往期内容也很精彩。每周更新面试高频算法题。从思路,代码,时间复杂度等多方面进行分析。
后台点击电子书可以获得:halfrost大神的孔雀书,经典巨著:算法导论。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值