LeetCode接雨水I、II

本文题图来源于: LeetCode

接雨水I

问题重述:

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

leetcode

上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)

示例:

输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6

思路分析:

题目给定了一些柱子,我们需要计算这些柱子之间能接住多少雨水。我们知道一个柱子上面最终能放多水是由它左右两边最高的两个柱子中矮的那个来决定的。最简单的办法就是依次遍历左右找最大然后取最小。这样的算法复杂度就是O( n 2 n^2 n2)。

我们现在考虑尝试在O(n)的时间复杂度内解决它,既然我们需要知道一个柱子左右两边的最大值,那么我们可以专门来建立两个数组分别去存储一个柱子左边的最高柱和右边的最高柱,这都可以在线性时间内完成,然后遍历他们取最小值就是这个柱子最终能装水装到的高度,这个我们就得到了每个柱子的装水量。

C++代码:
class Solution {
public:
    int trap(vector<int>& height) {
        if(height.size()<3)//如果柱子数小于3则无法装水
            return 0;
        vector<int>left,right;//创建两个数组去存储柱子左右最高柱的高度
        left=height;
        right=left;
        int Max=height.front();//初始化Max为首元素找左边最大值
        for(int i=1;i<height.size();i++)//从第二个数开始边界不会装水
        {
            Max=max(Max,height[i]);//每次更新Max确保它是左边最大
            left[i]=Max;//记录当前位置柱子左边的最大值
        }
        Max=height.back();//同理初始化Max为尾元素找右边最大值
        for(int i=height.size()-2;i>=0;i--)//从倒数第二个数开始边界不会装水
        {
            Max=max(Max,height[i]);//每次更新Max确保它是左边最大
            right[i]=Max;//记录当前位置柱子右边的最大值
        }
        int ans=0;
        for(int i=1;i<height.size()-1;i++)//遍历中间的柱子
        {
            ans+=(min(left[i],right[i])-height[i]);//中间柱子装水量是左右两边最高柱子中的最小值减去柱高
        }
        return ans;//返回答案
    }
};

接雨水II

问题重述:

给定一个 m x n 的矩阵,其中的值均为正整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。

说明:

m 和 n 都是小于110的整数。每一个单位的高度都大于 0 且小于 20000。

示例:

给出如下 3x6 的高度图:
[
[1,4,3,1,3,2],
[3,2,1,3,2,4],
[2,3,3,2,3,1]
]
返回 4。
在这里插入图片描述
如上图所示,这是下雨前的高度图[[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]] 的状态。
在这里插入图片描述
下雨后,雨水将会被存储在这些方块中。总的接雨水量是4。

思路分析:

此题是将接雨水I从二维扩展到了三维这样问题就麻烦了许多,我们并不能简单的将二维的左右变成上下左右。因为此时一个柱子能装多少水并不是由它同行同列的柱子高度直接决定的,因为它同行同列的柱子装多少水也受自身所在行列柱子的约束,所以一个柱子最终能装多少水受周围所有柱子的影响。

一个可以解决此题的办法就是收缩边界,首先我们知道边界是无法装水的,只有中间的柱子可能能装水,但因为特靠中间的柱子能装多少水是受周围所有柱子的影响的那并不好确定。但是与边界相邻的柱子能装多少水是可以确定的,准确的说是与最小的边界相邻的柱子能装多少水是可确定的。

我们从小到大排列所有的柱子,同时标记所有为边界的柱子,然后我们遍历柱子收缩边界。如果遍历的柱子不是边界那么我们跳过,如果是那么我们就将此边界收缩,即我们检查与它相邻、没被访问且不是边界的柱子。

如果这个柱子不高于我们出发的边界,那么它能装的水最多就到此边界,因为它相邻的是边界再多水就流走了,其次这是当前最矮的边界我们不会装比这个边界还少的水了。如此一来这个柱子能装的水就确定了,我们将这个柱子加水到这个高度然后将它标记为新的边界继续检查它周围的柱子。

直到我们找到一个柱子(也有可能一开始就是)高于我们出发的边界,那么它就一定无法装水了,因为装多少都会从边界流走。所以我们只需直接将它标记为新的边界就行了,因为我们一开始排列了所有的柱子,所以后面我们一定会遍历到它。

这样我们就能逐渐收缩我们的边界,直至遍历完所有的柱子。此算法的复杂度为O(nm)。

C++代码:
class Solution {
public:
    int ans,n,m;//定义答案和行列
    vector<vector<int>>mark,rain,edge;//分别定义标记数组、柱高数组、和边界数组
    int posx[4]={1,-1,0,0};//定义方位数组
    int posy[4]={0,0,-1,1};
    void dfs(int x,int y)//得到边界柱子向内收缩
    {
        mark[x][y]=0;//标记这个柱子已被遍历
        for(int i=0;i<4;i++)//检查各个方位
        {
            int a=x+posx[i];
            int b=y+posy[i];
            if(a>=0&&a<n&&b>=0&&b<m&&edge[a][b]==0&&mark[a][b])//如果这个方位没有过界且不是边界且未被访问那么我们就扩展它
            {
                edge[a][b]=1;//将它定为新的边界
                if(rain[a][b]<=rain[x][y])//如果这个柱子小于等于出发的边界那么它能装的雨水就是边界减去它的柱高
                {
                    ans+=(rain[x][y]-rain[a][b]);//将它能装的雨水加入答案
                    rain[a][b]=rain[x][y];//更新这个柱高为边界
                    dfs(a,b);//继续由它向内扩展
                }
            }
        }
    }
    int trapRainWater(vector<vector<int>>& heightMap) {
        n=heightMap.size();
        if(n<3)//如果行列数小于3说明柱子全是边界无法装水、装水为0
            return 0;
        m=heightMap[0].size();
        if(m<3)
            return 0;
        ans=0;//初始化答案为0
        mark.resize(n,vector<int>(m,1));//初始化标记数组1代表没探索过0表示探索过
        edge.resize(n,vector<int>(m,0));//初始化边界数组1代表是边界过0代表不是边界
        rain=heightMap;//将柱高传给全局变量
        vector<pair<int,pair<int,int>>>temp;//定义一个temp用来存储柱子的信息包括柱高、位置
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                temp.push_back({heightMap[i][j],{i,j}});//将柱子的信息存入temp数组
                if(i==0||j==0||i==n-1||j==m-1)
                {
                    edge[i][j]=1;//同时将边界标记
                }       
            }
        }
        sort(temp.begin(),temp.end());//将temp数组按柱高排序
        for(auto node:temp)//开始遍历排列后的柱高
        {
            int a=node.second.first;//获得柱子的位置
            int b=node.second.second;
            if(edge[a][b])//如果柱子是边界则收缩边界
                dfs(a,b);
        }
        return ans;//返回答案
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值