前言
题目描述
给你一个 m x n 的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。
呜呜呜 有一维的接雨水还不够,还来了一个二维的…
解题思路
- 把每一个元素称作块。因为那个图片给的好像瓷砖啊。
- 其实做这题一开始都是想的是对于每一个块,去找它四个方向最高的高度中的最小值(二维下则是左右最高的高度取较小的那一个)作为上界,当前块作为下界
但是这4个方向每次遍历复杂度过高,且不能像二维那样去提前预存每个方向的最大值- 那可以反过来我不以每个块为处理单元,而是以块的四周作为处理单元
- 那如何保证所有四周的可能性都考虑到呢? 我们从矩阵的最外围往里面遍历,像一个圈不断缩小的过程
- 为了防止重复遍历用visited记录
- 其次要用小顶堆(以高度为判断基准)来存入所有快的四周(即圈是不断缩小的,小顶堆存的就是这个圈)
- 为什么要用小顶堆? 这样可以保证高度较小的块先出队
** 为什么要让高度较小的块先出队?(关键点)
- 一开始时候就讲了基础做法是:对于每一个块,去找它四个方向最高的高度中的最小值(二维下则是左右最高的高度取较小的那一个)作为上界,当前块作为下界
- 而我们现在反过来不是以中心块为处理单元,而是以四周作为处理单元
- 我们如果能确保当前出队的元素对于该中心块来说是它周围四个高度中的最小值那么就能确定接雨水的大小
- 为什么队头元素的高度比中心块要高它就一定是中心块周围四个高度中的最小值呢?
因为我们的前提就是小顶堆:高度小的块先出队,所以对于中心块来说,先出队的必然是中心块四周高度最小的那一个- 步骤:
- 构建小顶堆,初始化为矩阵的最外围(边界所有元素)
- 不断出队,倘若队头元素大于其四周的
即代表能够接雨水:队头元素减去该中心块即当前中心块能接雨水的值- 但是接完雨水之后中心块还要存进队列中,但这时要存入的中心块是接完雨水后的中心块
代码
#include<iostream>
#include<queue>
#include<vector>
using namespace std;
class Solution{
public:
int trapRainWater(vector<vector<int>> &heights){
int n=heights.size(); //n是行
int m=heights[0].size(); //m是列
//priority_queue<pair<int,int>,vector<pair<int,int>>,decltype(&cmp) > q(cmp);
priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> q;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(i == 0 || j==0 ||i==n-1 || j==m-1){
//将周围一圈入优先级队列
q.push({heights[i][j],i*m+j}) ;
heights[i][j]=-1;
}
}
}
int x[4]={0,0,1,-1},y[4]= {1,-1,0,0};
int ans=0;
int mh=0;
while(!q.empty()){
auto &cur=q.top();
int h=cur.first;
int i=cur.second/m;//i,j表示的是当前的坐标
int j=cur.second%m;
q.pop();
if(h > mh) mh=h;
for(int k=0;k<4;k++){
i=i+x[k];
j=j+y[k];
if(i>=0 && i<n && j>=0 &&j<m && heights[i][j]!=-1){
q.push({heights[i][j],i*m+j});
if(heights[i][j]<mh){
ans=ans+mh-heights[i][j];
heights[i][j]=-1;
}
}
i=i-x[k];//改变方向
j=j-y[k];
}
}
return ans;
}
};
int main(){
int m,n;//m是行 n是列
int data;
vector<vector<int>> heights;
cin>>m>>n;
for(int i=0;i<m;i++){
vector<int> row;
for(int j=0;j<n;j++){
cin>>data;
row.push_back(data);
}
heights.push_back(row);
}
int res=Solution().trapRainWater(heights);
cout<<res<<endl;
return 0;
}