Leetcode 热题100 42.接雨水(C++ 多种解法,错过可惜)

1、题目描述

这是博主今天的Leetcode 每日一题——接雨水(困难),这道题做了一天也没有做出什么花样来,最后看了题解后,发现居然有那么多种解法,但是自己一个也没有想到,真的是被自己菜哭的一天 ,博主就根据自己的理解,写了这篇题解,希望对其他的小伙伴有所帮助。(插一句题外话,之所以写这篇题解,是因为它的解法很多,个人感觉很有价值,也有助于博主日后的再巩固,提供方便)话不多说,我们来看看,几种解法。(博主个人的理解哦)

2、题解集

2.1暴力解法

首先,暴力解法是一定是超时(自己已经试过了),如果不想看的小伙伴可以自动跳过,但是,博主在这里依然要写这种方法的原因是有的时候,我们可能想不到正解,只能通过题意或者自己的理解写出超时的代码(有的甚至都写不出来),但是这在样的代码上如果有优化,有可能就是正确解!那么就让我们来看看暴力解法的具体实现吧

①:我们枚举数组中的每一个元素,以该元素为分界线,找出其左边的最大值(left_max),再找出其右边的最大值(right_max),然后求left_max和right_max中的最小值。(为什么求最小值,就像木桶原理一样,最短的木板决定了,木桶最终的盛水量,后面的题解中也是这个意思,就不再解释)

②:我们将求得的最小值-该元素的值,即为该处的积水量,然后用ans来累加记录。

代码展示:

class Solution {
public:
    int trap(vector<int>& height) {
     int n=height.size();
     if(n==1)
     return 0;
    int ans=0;
     for(int i=1;i<n;i++)
     {
        int left_max=0,right_max=0;//用于记录每个元素左边与右边的最大值
        for(int j=i;j>=0;j--)//以该元素为分界线找左边的最大值
        {
            left_max=max(left_max,height[j]);
        }
        for(int j=i;j<n;j++)//以该元素为分界线找右边的最大值
        {
            right_max=max(right_max,height[j]);
        }
         ans+=min(left_max,right_max)-height[i];//累计求雨水量
     }
     return ans;//返回雨水量的值
    }
};

2.2动态编程

 对于动态编程的思想,博主个人觉得就是对暴力解法的进一步优化,我们都知道暴力解法是求一个元素左边的最大值与右边的最大值,求两者之间的最小值,再与该元素求差,作为该处的积水量,而在动态编程中:

①:我们首先用两个动态数组left_max[],right_max[],分别保存从左到右(和从右到左的),每个元素左边的最大值(右边的最大值)。

②:然后再通过遍历一遍数组,比较每个元素对应的min(left_max[],right_max[])-该处的元素值,就是该处雨水的积水量,最后用ans来累加即可,这样我们就把O(n*n)的时间复杂度将为O(n)了。

③:至于left_max[],right_max[]的求解,博主个人感觉有点求“前缀和”和“后缀和”的感觉,只不过对于left_max[],right_max[]来说,是求比较后的最大值,而不是累加和;

代码展示

class Solution{
    public:
    int trap(vector<int>& height){
        int n=height.size();
        if(n==1)
        return 0;
        int ans=0;
        vector<int>left_max(n),right_max(n);//定义好两个动态数组
        left_max[0]=height[0];//保存left_max[]的第一个值
        for(int i=1;i<n;i++)//一次将left_max[]的每个元素赋值
        {
            left_max[i]=max(left_max[i-1],height[i]);
        }
        right_max[n-1]=height[n-1];//保存left_max[]的第一个值
        for(int i=n-2;i>=0;i--)//一次将right_max[]的每个元素赋值
        {
        right_max[i]=max(right_max[i+1],height[i]);
        }
        for(int i=0;i<n;i++)//遍历数组,求得每个区域对应的积水量
        {
            ans+=min(left_max[i],right_max[i])-height[i];//累加加积水量
        }
        return ans;//返回积水量的值
    }
};

2.3双指针

来啦来啦,双指针它来了,不得不的说双指针的解法真的很妙,怎么一个妙法呢?让我慢慢道来。

首先我们在动态编程的方法中知道,当left_max[i]<right_max[i]时,此时,积水量取决于left_max[i]的高度,同理left_max[i]>right_max[i],积水量取决于right_max[i]的高度。所以我们可以这样理解,先用两个指针分别指向,原数组的头和尾(两端),对于其中某一元素,如果右端指针所指的值大,那么积水的高度就依赖于当前方向的高度(从左到右),如下图:

                                 right_max
 left_max                             __
   __                                |  |
  |  |__   __??????????????????????  |  |
__|     |__|                       __|  |__
        left                      right

 因为此时,从左到右的数对该数(left)来说是绝对可信的,而右边的数不一定,同理来说,当左端的数大时,那么积水的高度就依赖于当前方向的高度(从右到左),有了这样的思想我们就可以有一下的操作了:

①:定义left和right分别指向原数组的左右两端的下标;

②:用left_max,right_max分别保存左右两端的最大值;

③:如果,height[left]<height[right],则判断 if height[left]>left_max,则更新left_max,否则left_max-height[left]保存积水量;若height[left]>=height[right],则判断 if height[right]>right_max,则更新right_max,否则right_max-height[left]保存积水量;

然后让我们来看看具体的代码实现吧。

代码展示

class Solution{//双指针
    public:
    int trap(vector<int>& height){
        int n=height.size();
        if(n==1)
        return 0;
        int left=0,right=n-1;//左右边界
        int left_max=0,right_max=0;
        int ans=0;
        while(left<right)
        {
            if(height[left]<height[right])//对该数左边的数进行操作
            {
                if(height[left]>left_max)
                {
                    left_max=height[left];//更新left_max;
                }
                else
                {
                    ans+=left_max-height[left];//计算该处的积水量
                }
                left++;//向右扫描
            }
            else//对该数的右边的数进行操作
            {
                if(height[right]>right_max)
                {
                    right_max=height[right];//更新right_max;
                }
                else
                {
                    ans+=right_max-height[right];//计算该处的积水量
                }
                right--;//向左扫描
            }
        }
        return ans;//返回积水量
    }
};

总结

这道题考到了好几种方法,我们的解题过程,解题方法也是一步一步的优化,一步一步递进的,博主个人觉得我们在平时的解题的时候也应该这样来做,总的来说这道题对我的收获也是很大的,如果有帮助到正在阅读的小伙伴,希望记得点赞、收藏哦!

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值