题目
给定一个 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。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/trapping-rain-water-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
这题一开始我是想仿造【LeetCode 42:接雨水】的思路,即当前连通分量中的边界最小值,都大于当前点,那么这个点就可以装水,bfs计算连通分量内的最小高度,但是超时
扫描二维码关注公众号,回复:
10088895 查看本文章
超时代码(正确的在下边
class Solution {
public:
typedef struct p
{
int x, y, h;
p():x(0), y(0), h(0){}
p(int a, int b, int c):x(a),y(b),h(c){}
}p;
int trapRainWater(vector<vector<int>>& heightMap)
{
if(heightMap.size()<3) return 0;
if(heightMap[0].size()<3) return 0;
int m=heightMap.size(), n=heightMap[0].size();
int biasX[4]={-1,0,1,0}, biasY[4]={0,-1,0,1};
int ans = 0;
for(int i=1; i<m-1; i++)
{
for(int j=1; j<n-1; j++)
{
int minh=INT_MAX;
queue<p> q; q.push(p(i, j, heightMap[i][j]));
vector<p> path;
bool vis[m][n]; memset(vis, false, sizeof(vis)); vis[i][j]=true;
while(!q.empty())
{
p tp=q.front(); q.pop(); path.push_back(tp);
for(int b=0; b<4; b++)
{
int nx=tp.x+biasX[b], ny=tp.y+biasY[b];
if(0<=nx && nx<m && 0<=ny && ny<n)
{
if(!vis[nx][ny])
{
if(heightMap[nx][ny]>heightMap[tp.x][tp.y])
minh = min(minh, heightMap[nx][ny]);
else
q.push(p(nx, ny, heightMap[nx][ny])), vis[nx][ny]=true;
}
}
else minh = 0;
}
}
// cout<<i<<" "<<j<<" "<<minh<<" "<<maxh<<"-"<<endl;
if(heightMap[i][j]>=minh) continue;
for(int k=0; k<path.size(); k++)
{
ans += minh - heightMap[path[k].x][path[k].y];
heightMap[path[k].x][path[k].y] = minh;
}
i=1; j=1;
//cout<<i<<" "<<j<<" "<<ans<<endl;
}
}
return ans;
}
};
正确思路
因为能够装多少水,是由当前已知边界的最低点决定的
维护一个优先队列作为当前已知边界的集合,方便快速获取已知边界的最低点,每次选择最低的边界,查看边界四周的格子能否装水
四周的格子一共就两种情况:能装水,不能装水
如果周边的格子能装水,即矮于当前最低边界
如果已知的最低边界,都要高于它旁边的格子,那么这个格子装水之和必定会变成这样,也就是水高不过已知的最低边界
因为这个格子装了水,那么以后不再考虑它,也就是,我们把边界更新了,注水的格子和墙是无异的,我们认为新边界是和旧边界一样高的
如果周边的格子不能装水,即高于当前最低边界
如果当前格子比当前已知最低边界还要高,那么边界将被更新,也就是将当前格子加入边界集合,而旧边界则退出边界集合
总体思路是:
- 维护一个优先队列作为当前已知边界的集合,初始元素是矩阵四边的元素
- 每次选取最低的已知边界x,将它从集合中删除
- 检查最低边界x周围的格子
g
,格子g的高度是gh
,如果低于x,那么可以装水,计算水量,然后将【坐标为g,高度为x】的边界加入集合,因为这里是用最低的边界做计算,保证了正确性,而加入则是加等价边界 - 如果高于,那么可以更新边界,将【坐标为g,高度为gh】的边界加入集合
- 注意对操作过的边界添加visited访问控制,以免重复访问
代码
class Solution {
public:
typedef struct p
{
int x, y, h;
p():x(0), y(0), h(0){}
p(int a, int b, int c):x(a),y(b),h(c){}
bool operator < (const p &p1) const {return p1.h<h;}
}p;
int trapRainWater(vector<vector<int>>& heightMap)
{
if(heightMap.size()<3) return 0;
if(heightMap[0].size()<3) return 0;
int m=heightMap.size(), n=heightMap[0].size();
int biasX[4]={-1,0,1,0}, biasY[4]={0,-1,0,1}, ans = 0;
bool vis[m][n]; memset(vis, false, sizeof(vis));
priority_queue<p> q;
// 初始化:将矩形的四边加入边界集合
for(int i=0; i<m; i++)
{
q.push(p(i,n-1,heightMap[i][n-1])); vis[i][n-1]=true;
q.push(p(i,0,heightMap[i][0])); vis[i][0]=true;
}
for(int i=1; i<n-1; i++)
{
q.push(p(0, i, heightMap[0][i])); vis[0][i]=true;
q.push(p(m-1, i, heightMap[m-1][i])); vis[m-1][i]=true;
}
while(!q.empty())
{
// 每次选取最低的已知边界,更新四周
p tp=q.top(); q.pop();
for(int b=0; b<4; b++)
{
int nx=tp.x+biasX[b], ny=tp.y+biasY[b];
if(0<=nx && nx<m && 0<=ny && ny<n && !vis[nx][ny])
{
vis[nx][ny] = true;
q.push(p(nx, ny, max(heightMap[nx][ny], tp.h)));
ans += max(0, tp.h-heightMap[nx][ny]);
}
}
}
return ans;
}
};