浅谈leetcode11题盛最多水的容器和42题接雨水

11题要和42题结合起来看,42就是11的一个小变化,双指针类型的题目要求我们把握前后指针移动的过程,并且要明白每次移动的时候我们需要根据要求做哪些操作,不仅仅是一维可以使用双指针,在不少二维矩阵的情况中也可以通过双指针来解决问题

11. 盛最多水的容器

本题是一道非常经典的面试题,题目要求我们找出其中的两条线,使其能容纳尽量多的水

常规暴力的做法就是 O ( n 2 ) O(n^2) O(n2),两个for直接遍历,但这种做法非常的暴力,显得非常不优美,那让我们来看看双指针是如何来解决这道题的

双指针,顾名思义,我们需要有2个指针来进行操作

在初始时,我们将2个指针分别指向数组的最左端和最右端,如下面中的数据所展示的那样

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

我们要求的盛水量 = 数组中两数的距离 * 选取的两数中最小的值

如果我们选取的是首尾这2个值的话,那我们可以得到最长的两数距离,对应盛水量 = m i n ( 1 , 7 ) ∗ 8 = 8 min(1,7)∗8=8 min(1,7)8=8

通过观察数据我们可以很明显的发现,只是两个数的距离最长不一定能盛到最多的水,就像我们常说的木桶效应,一只水桶能装多少水取决于它最短的那块木板,我们这个选取中最小的数仅仅只有1,是整个数组的数值中最小的,这也就导致了值必定很小,即使尾部和它选择最小的那个值是10000,通过min取最小,也只能是1

因此我们需要移动指针位置,我们发现右指针指向的值为7,左指针指向的值为1,那么为了降低我们的短板,我们肯定希望拿掉1,将1替换成更高的值,因此我们向右移动左指针的位置,将其指向了8

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

重新进行公式计算,对应盛水量 = m i n ( 8 , 7 ) ∗ 7 = 49 min(8,7)∗7=49 min(8,7)7=49,可以发现值远远大于了第一次的8,但这个时候我们发现短板变成了右边的7,因此我们移动右指针向左移动,继续重新进行计算。

重复上述的行为,进行反复操作,我们最终左右指针会指向同一个元素,这也意味着我们求值操作的结束,因此此时数组中两数的距离变为了0,没有继续计算下去的意义了

通过双指针的做法,我们将算法的时间负责度一下子从 O ( n 2 ) O(n^2) O(n2)降低为了 O ( n ) O(n) O(n),代码并没有复杂多少,效率一下子就提高了

下面通过代码展示我们上述的思想,希望大家能够理解

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

class Solution {
public:
    int maxArea(vector<int>& height) {
        int l = 0, r = height.size() - 1;
        int ans = 0;
        while(l < r){
            int area = min(height[l], height[r]) * (r - l);
            ans = max(ans, area);
            if(height[l] <= height[r]){
                ++l;
            }else{
                --r;
            }
        }
        return ans;
    }
};

// #define DEBUG

#ifdef DEBUG
int main(){
    
    return 0;
}
#endif

42. 接雨水

42题是11题的一个小小的变种,它要求的就是我们接到雨水的总量,不同于11题选取任意2个,此题要求整体接雨量,并且每个柱子也有体积问题需要进行考虑哟

本题依然可以采用双指针的方法,但是本题比11题多了2个需要维护的变量,分别是lMax左最大高度和rMax右最大高度

在双指针的移动过程中,不同于上一题每次移动1次指针,我们求取一次整体的盛水量,本题我们每移动一次指针,都是计算当前坐标的盛水量

[0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
 ^                                ^

首先初始的时候,我们的头指针指向第一个位置,尾指针指向最后一个位置,初试由于左部我们一开始没有挡水的柱子,所以IMax=0,右部同理设为rMax=0

第一轮我们不需要计算,因为第一个位置左边和最后一个位置右边由于都没有挡水的柱子,所以我们一开始这2个位置必定无法接水

这里我们怎么判断我们移动左指针还是右指针的?

我们首先明白一点,左右柱子高度必定是可以比较的,只有三种情况,左边高,一样高,右边高

当右边的柱子(高度1)比左边的柱子(高度0)高的时候,我们知道了左指针这个位置的挡板最低

因此当前左指针位置的取雨水的数值 = 1 * (0 - 0) = 0,并将求得的结果累加到ans中

[0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
    ^                             ^

操作完后,我们右移了一次左指针,左指针指向的值变为了1,此时我们更新左边最高挡板IMax的值为1,通过比较发现IMax和rMax当前的值相同,此时其实我们移动哪一边的指针都可以,我们这就按移动右指针为标准

当前右指针指向的挡板位置就是最高的右部分挡板,所以

当前右指针位置的取雨水的数值 = 1 * (1 - 1) = 0,并将求得的结果累加到ans中

[0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
    ^                          ^

右指针移动后,rMax更新为2,由于左指针指的柱子高度比右指针指向的柱子高度低,所以我们重复一开始的操作,左指针位置的取雨水的数值 = 1 * (1 - 1) = 0,将求得的结果累加到ans中

[0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
       ^                       ^

再次移动左指针的值,我们此时lMax不用更新了,因为当前值比lMax更小

左指针位置的取雨水的数值 = 1 * (1 - 0) = 1,将求得的结果累加到ans中

之后重复上述的操作,就可以得到正确的答案了

如果还不是很明白为什么要移动柱子高度低的指针,可以看一下官方题解的动态规划的维护leftMax[]和rightMax[]两个数组的思想,最后ans += min(leftMax[i], rightMax[i]) - height[i];

双指针的这个移动方案可以确保移动的过程中,需要移动左指针的时候,最小就是lMax,右指针同理。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

class Solution {
public:
    int trap(vector<int>& height) {
        int ans = 0;
        int l = 0, r = height.size() - 1;
        int lMax = 0, rMax = 0;
        while (l < r) {
            lMax = max(lMax, height[l]);
            rMax = max(rMax, height[r]);
            if (height[l] < height[r]) {
                ans += lMax - height[l];
                ++l;
            } else {
                ans += rMax - height[r];
                --r;
            }
        }
        return ans;
    }
};

// #define DEBUG

#ifdef DEBUG
int main(){
    
    return 0;
}
#endif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值