LeetCode--42.接雨水
前言:昨天好像又断更一天了,前天晚上一直在玩云顶之弈,一直输,怒火中烧,有一点上头了,我是一个不服输的人,所以,玩到了凌晨四点吧,应该是在玩家对战的环节睡着了,一觉醒来已经是下午两点了,所以断更了,今天续上,痛定思痛,怎么暑假了,我这么堕落,所以,以后应该不会再断更了
解题思路:
1.获取信息:
给定n个非负整数表示每个宽度为1的柱子的高度图
要求是,计算按次排列的柱子,下雨后能接多少水
它还给了一个图,看着理解吧
(这道题给出的信息比较简练,没那么难以理解,所以说,比较难的是思路)
2.分析题目:
给定了这么多根柱子,也知道了它们的排序情况,也就是位置情况
如果下雨,要接雨水的话,那么我们知道一定是一个凹的形状才可与接到雨水对吧
(其实我看到这道题,让我想到了力扣第11题,成最多水的容器,所以我后续用这个思路也写了一种方法)
我这次自己写了并且看了一些别人的解法,所以一共写了4种方法,因为方法比较多,所以它们的思路我会在尝试编写代码环节中借着代码来给你逐一讲解
3.示例查验:
你可以用这些示例来检验你的思路是否正确
4.尝试编写代码:(每个思路我只是说了中心的思路,要细致一点的话,就去看代码及其注释,这样是方便你先去自己思考一下,然后再去看代码对答案,可以帮你显著提升哦,是不是很贴心)
(1)暴力法:
思路:我们前面说了要找凹形的地方,只有这些地方才能接到水,也就是左边单调递减,右边单调递增,单调递增的区域和单调递减的区域共同构成了这个可与盛水的容器,那我们只用找到该容器的左端点和右端点来计算这个容器的盛水量即可,把每个盛水容器的盛水量相加就是答案
class Solution {
public:int trap(vector<int>& height) {int size=height.size();//柱子的数量,(0也算作一个柱子,不然你很难理解我的思路)if(size<3)return 0;//至少存在三个柱子才可以构成一个盛水容器int left=0;//用来记录左端点int res=0;//总盛水量while(left<size-1){//左端点没有超过末尾位置if(height[left]<=height[left+1]){//找左端点时,如果呈单调递增left++;continue;}//一直找到不呈单调递增的位置,作为左端点int right=left+1;//开始查找右端点for(int i=right+1;i<size;i++){if(height[i]>height[right]){//如果呈单调递增,右端点就往右移right=i;}if(height[i]>=height[left]){//如果右边的点大于了左端点的高度right=i;break;}}int h=min(height[left],height[right]);//开始计算盛水量,就不多说了for(int i=left+1;i<right;i++){res+=(h-height[i]);}left=right;//将左端点移动到右端点的位置}return res;}
};
(2)动态规划:
思路:我们前面的方法是寻找端点,那么我们可不可以换一个角度思考问题
比如,我们不找端点了,我们寻找端点中间的点,也就是可以盛水的区块
我举一个例子吧,端点相当于盒子的底上面的四条边,它们是环绕着水的,而盒子的底的上面才是水,水被盒子的四条边和盒子的底包围起来,这是一个存水的容器
上面我们相当于是求四条边来算出盒子的盛水量的,这次我们来算底,来求出盒子的盛水量
那么我们怎么根据盒子的底来算出盛水量呢?(下面的思路描述不是上面那个例子的方盒模型咯,不要搞错了,上面只是举个例子而已,跟这道题不搭边)
我们知道底的盛水量,还跟左右的高有关
到我们走到某个底的时候,需考虑左右两边的高
所以我们需要知道,当走到下标为 i 的地方时,需要知道小于等于 i 的区间中最高的端点是哪个,还有大于等于 i 的区间中最高的端点是哪个
所以,可以运用动态规划的思想,我们从左到右,可以遍历出小于等于 i 的区间中最高的端点是哪个,从右到左可以遍历出大于等于 i 的区间中最高的端点是哪个
动态规划是,通过小问题来推出大问题,所以我会初始化一些值
class Solution {
public:int trap(vector<int>& height) {int size=height.size();if(size<3)return 0;vector<int>leftMax(size,0);//每个i的左边(包括i)最大的高是vector<int>rightMax(size,0);//每个i右边(包括i)最大的高是int res=0;leftMax[0]=height[0];//初始化基底for(int i=1;i<size;i++){//左区间leftMax[i]=max(height[i],leftMax[i-1]);}rightMax[size-1]=height[size-1];//初始化基底for(int i=size-2;i>=0;i--){//右区间rightMax[i]=max(height[i],rightMax[i+1]);}for(int i=0;i<size;i++){//计算每个底的盛水量,并相加res+=(min(leftMax[i],rightMax[i])-height[i]);}return res;}
};
(3)栈方法:
力扣,那个最长有效括号给了我一点启发吧
思路:你有没有见过括号,括号的话,我们知道它根据自己的相对位置,是遵循就近原则来进行配对的
我们这次使用一个栈,栈具有先进后出的性质,所以我们可以将所有的左端点放入进去,当遇到一个大于栈顶值的数时,作为右端点,就可以进行配对
注意哦,我说的左端点,指着是,单调递减区间的每个数都是左端点,而作为右端点的,也是单调递增区间中的每个数,这么看,是不是想许多括号嵌套起来,而我们只是配对每队括号而已
感觉我讲不清楚,还是看代码及其注释吧
class Solution {
public:int trap(vector<int>& height) {int size=height.size();stack<int>s;int Val=0;for(int i=0;i<size;i++){//从左到右开始遍历while(!s.empty()&&height[i]>height[s.top()]){//如果栈为空,且下标为i的位置上的数大于栈顶中存储的下标的位置上的数int top=s.top();//取出栈顶元素s.pop();//弹出栈顶元素if(s.empty())break;//如果栈为空,则推出循环int left=s.top();//取出栈顶元素int Width=i-left-1;//计算盛水量int Height=min(height[i],height[left])-height[top];Val+=Width*Height;}s.push(i);//压入下标}return Val;}
};
(4)双指针法:
力扣第11题,盛最多水的容器给了我启发吧
思路:我们在力扣第11题中,我们是讲一个指针放在了首位,一个指针放在了末尾,然后根据情况往中间移动,对吧
这道题可不可以呢?
当然可以,我们可以看作是一排一排进行填水操作的,进行第一排时,那些柱子长度大于等于1的柱子,如果存在至少两个的话,就留得住,进行第二排时,同理
所以,只有存在至少两个高度相同或者其中一个大于量一个柱子得情况,那么在小于等于这两个柱子比较小得那个柱子得高度的填水操作都可以确保留水
我感觉我还是每讲清楚,你还是看代码及其注释吧
class Solution {
public:int trap(vector<int>& height) {int size=height.size();int n=0;//水平面int left=0,right=size-1;int Val=0;while(left<=right){//如果左指针小于等于右指针if(left==right&&height[left]<n){//如果左指针等于右指针且指向的位置的数小于水平面Val+=(n-height[left]);break;}int Min=min(height[left],height[right]);//求出两个端点可存储的最小的水平面if(Min>n)n=Min;//如果大于此时的水平面,就更新水平面,使水平面更高if(height[left]<height[right]){//如果左端点小于右端点if(left<right&&height[left]<n)Val+=(n-height[left]);left++;}else if(height[left]>height[right]){//如果右端点小于左端点if(left<right&&height[right]<n)Val+=(n-height[right]);right--;}else{//如果左右端点相等if(left<right&&height[left]<n)Val+=(n-height[left]);if(left<right&&height[right]<n)Val+=(n-height[right]);left++;right--;}}return Val;}
};
好了,这次的题解就到这里了,我要好好休息一下了,晚安
今天就不给忠告了,偶尔特殊一次也不错