正经解法比较复杂,这里贴出一个相对简单的解法。
题目描述 给你一个 m x n 的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。
1 2 1 <= m,n <= 200 0 <= heightMap[i][j] <= 2*10^4
示例1
1 2 3 输入: heightMap = [[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]] 输出: 4 解释: 下雨后,雨水将会被上图蓝色的方块中。总的接雨水量为1+2+1=4。
示例2
1 2 输入: heightMap = [[3,3,3,3,3],[3,2,2,2,3],[3,2,1,2,3],[3,2,2,2,3],[3,3,3,3,3]] 输出: 10
思路分析 首先需要明确一点,该题中不存在“空洞”,否则数据不足以描述房屋的状态。即不存在以下(左视图)情况:
引理:沿水平方向任切一刀去除下层后,接水体积只会减少被切除部分中水的体积(若存在)。
解释:考虑使用一个锋利的铁板去切,切后仍用铁板托住上层,则上下两层的水均不会流出。
由引理,问题规模缩小。
代码实现(无优化) 记最高高度为MAXH
,从第一层(从上往下数)开始切,for k 1 to MAXH
,则切下来的上层可记为MAX(0,a[i][j]-MAXH+k)
,对新的矩阵dfs元素为零的连通块,同时记录连通块大小。注意若连通块通向边界,则水流出,该连通块作废。遍历完第k层后k++,继续计算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 #include <cstdio> #include <cstring> #include <vector> #define MAX(a,b) ((a)>(b)?(a):(b)) int dir[4 ][2 ] = {{0 ,1 },{-1 ,0 },{0 ,-1 },{1 ,0 }};int a[203 ][203 ], m, n, MAXH = -1 , ans;bool vis[203 ][203 ];using std::vector;int dfs (int x,int y,bool & _myflag,int k) { if (vis[x][y]) return 0 ; vis[x][y]=true ; int cnt=1 ; if ((x==1 )||(x==m)||(y==1 )||(y==n)){ _myflag=false ; } for (int p=0 ;p<4 ;p++){ int nx=x+dir[p][0 ]; int ny=y+dir[p][1 ]; if ((nx<1 )||(nx>m)||(ny<1 )||(ny>n)||vis[nx][ny])continue ; if (MAX (0 ,a[nx][ny]-MAXH+k))continue ; cnt+=dfs (nx,ny,_myflag,k); } return cnt; } class Solution {public : int trapRainWater (vector<vector<int >>& heightMap) { ans = 0 ; MAXH = -1 ; auto it1 = heightMap.begin (); m = heightMap.size (); n = it1->size (); for (int i = 1 ; i <= m; ++i) { for (int j = 1 ; j <= n; ++j) { a[i][j] = heightMap[i-1 ][j-1 ]; MAXH = MAX (MAXH, a[i][j]); } } for (int k=1 ;k<=MAXH;k++){ for (int i = 0 ; i < m+1 ; i++) for (int j = 0 ; j < n+1 ; j++) vis[i][j] = false ; for (int i=1 ;i<=m;i++){ for (int j=1 ;j<=n;j++){ if (MAX (0 ,a[i][j]-MAXH+k))vis[i][j]=true ; if (vis[i][j])continue ; bool myflag = true ; int tmp = dfs (i,j,myflag,k); if (!myflag) continue ; ans += tmp; } } } return ans; } };
这份代码的复杂度是 O(mn) 带着一个超大的常数,会超时。但是如果有一个层数不高、mn巨大的数据,或许这份代码会稍好一点。
所以要正经过题还是得看主流的解法 。
优化思路 染色。
对于合法的连通块,不再进行 dfs,改为查询已记录位置下方是否有零。
对于通向边界的连通块,仍然需要 dfs. 若合法,则需将其加入合法颜色。
该改进的原理是:因不存在空洞,下一层的零元连通块是上层零元连通块的子集。