一、网格类问题DFS框架
网格 DFS 遍历的框架代码:
void dfs(int[][] grid, int r, int c) { // 判断 base case // 如果坐标 (r, c) 超出了网格范围,直接返回 if (!inArea(grid, r, c)) { return; } // 访问上、下、左、右四个相邻结点 dfs(grid, r - 1, c); dfs(grid, r + 1, c); dfs(grid, r, c - 1); dfs(grid, r, c + 1); } // 判断坐标 (r, c) 是否在网格中 boolean inArea(int[][] grid, int r, int c) { return 0 <= r && r < grid.length && 0 <= c && c < grid[0].length; }
如何避免重复遍历
网格结构的 DFS 与二叉树的 DFS 最大的不同之处在于,遍历中可能遇到遍历过的结点。这是因为,网格结构本质上是一个「图」,我们可以把每个格子看成图中的结点,每个结点有向上下左右的四条边。在图中遍历时,自然可能遇到重复遍历结点。
如何避免这样的重复遍历呢?答案是标记已经遍历过的格子。以岛屿问题为例,我们需要在所有值为 1 的陆地格子上做 DFS 遍历。每走过一个陆地格子,就把格子的值改为 2,这样当我们遇到 2 的时候,就知道这是遍历过的格子了。也就是说,每个格子可能取三个值:
- 0 —— 海洋格子
- 1 —— 陆地格子(未遍历过)
- 2 —— 陆地格子(已遍历过)
void dfs(int[][] grid, int r, int c) { // 判断 base case if (!inArea(grid, r, c)) { return; } // 如果这个格子不是岛屿,直接返回 if (grid[r][c] != 1) { return; } grid[r][c] = 2; // 将格子标记为「已遍历过」 // 访问上、下、左、右四个相邻结点 dfs(grid, r - 1, c); dfs(grid, r + 1, c); dfs(grid, r, c - 1); dfs(grid, r, c + 1); } // 判断坐标 (r, c) 是否在网格中 boolean inArea(int[][] grid, int r, int c) { return 0 <= r && r < grid.length && 0 <= c && c < grid[0].length; }
二、解题
1、岛屿数量
给一个01矩阵,1代表是陆地,0代表海洋, 如果两个1相邻,那么这两个1属于同一个岛。我们只考虑上下左右为相邻。
岛屿: 相邻陆地可以组成一个岛屿(相邻:上下左右) 判断岛屿个数。
关键在于: 在进行遍历时,统计了该点处的岛屿后,处理掉其他的岛屿
class Solution { public: /** * 判断岛屿数量 * @param grid char字符型vector<vector<>> * @return int整型 */ void dfs(vector<vector<char>>& grid, int r, int c) { if(!inArea(grid, r, c)) return ; if(grid[r][c] != '1') return; grid[r][c] = '2'; dfs(grid, r-1, c); dfs(grid, r, c-1); dfs(grid, r+1, c); dfs(grid, r, c+1); } bool inArea(vector<vector<char>>& grid, int r, int c) { if(r>=0 && r <grid.size() && c >= 0 && c < grid[0].size()) return true; else return false; } int solve(vector<vector<char> >& grid) { // write code here int n = grid.size(); int m = grid[0].size(); int res = 0; for(int i=0; i<n; i++) { for(int j=0; j<m; j++) { if(grid[i][j] == '1') { res+=1; dfs(grid, i, j); } } } return res; } };
2、岛屿周长
岛屿的周长是计算岛屿全部的「边缘」,而这些边缘就是我们在 DFS 遍历中,dfs 函数返回的位置。观察题目示例,我们可以将岛屿的周长中的边分为两类,如下图所示。黄色的边是与网格边界相邻的周长,而蓝色的边是与海洋格子相邻的周长。
当我们的 dfs 函数因为「坐标 (r, c) 超出网格范围」返回的时候,实际上就经过了一条黄色的边;而当函数因为「当前格子是海洋格子」返回的时候,实际上就经过了一条蓝色的边。这样,我们就把岛屿的周长跟 DFS 遍历联系起来了,
class Solution { public: int dfs(vector<vector<int>>& grid, int r, int c) { if(!inArea(grid,r,c)) return 1; if(grid[r][c] == 0) return 1; if(grid[r][c] == 2) return 0; //已经遍历过的格子 grid[r][c] = 2; return dfs(grid, r-1, c) + dfs(grid, r, c-1) + dfs(grid, r+1, c) + dfs(grid, r, c+1); } bool inArea(vector<vector<int>>& grid, int r, int c) { return r>=0 && r<grid.size() && c>=0 && c<grid[0].size(); } int islandPerimeter(vector<vector<int>>& grid) { int n = grid.size(); int m = grid[0].size(); int res = 0; for(int i=0; i<n; i++) { for(int j=0; j<m; j++) { if(grid[i][j] == 1) res += dfs(grid,i,j); } } return res; } };
3、岛屿的最大面积
class Solution { public: bool inArea(vector<vector<int>>& grid, int r, int c) { return r>=0 && r<grid.size() && c>=0 && c < grid[0].size(); } int dfs(vector<vector<int>>& grid, int r, int c) { if(!inArea(grid, r, c)) return 0; if(grid[r][c] != 1) return 0; grid[r][c] = 2; return 1 + dfs(grid, r-1, c) + dfs(grid, r, c-1) + dfs(grid, r+1, c) + dfs(grid, r, c+1); } int maxAreaOfIsland(vector<vector<int>>& grid) { int n = grid.size(); int m = grid[0].size(); int res = 0; for(int i=0; i<n; i++) { for(int j=0; j<m; j++) { if(grid[i][j] == 1) { int temp = dfs(grid, i, j); res = max(res, temp); } } } return res; } };
4、人造岛的最大面积
两次遍历
class Solution { public: bool inArea(vector<vector<int>>& grid, int r, int c) { return r>=0 && r<grid.size() && c>=0 && c < grid[0].size(); } int area(vector<vector<int>>& grid, int r, int c, int index) { if(!inArea(grid, r, c)) return 0; if(grid[r][c] != 1) return 0; grid[r][c] = index; return 1 + area(grid, r-1, c, index) + area(grid, r, c-1, index) + area(grid, r+1, c, index) + area(grid, r, c+1, index); } /** * 对于海洋格子,找到上下左右 * 每个方向,都要确保有效inArea以及是陆地格子,则表示是该海洋格子的陆地邻居 */ set<int> findneibor(vector<vector<int>>& grid, int r, int c) { set<int> tmp; if (inArea(grid,r-1,c) && grid[r-1][c] != 0){ tmp.insert(grid[r-1][c]); } if (inArea(grid,r+1,c) && grid[r+1][c] != 0){ tmp.insert(grid[r+1][c]); } if (inArea(grid,r,c-1) && grid[r][c-1] != 0){ tmp.insert(grid[r][c-1]); } if (inArea(grid,r,c+1) && grid[r][c+1] != 0){ tmp.insert(grid[r][c+1]); } return tmp; } int largestIsland(vector<vector<int>>& grid) { int res = 0; int index = 2; map<int, int> mp; //岛屿编号, 岛屿面积 //第一遍遍历 记录岛屿大小 for(int i=0; i<grid.size(); i++) { for(int j=0; j<grid[0].size(); j++) { if(grid[i][j] == 1) { int Area = area(grid, i, j, index); mp.insert({index, Area}); index++; res = max(res, Area); } } } if(res == 0) return 1; //第二遍遍历,遍历海洋格子,假设这个格子填充,那么就把上下左右是陆地的格子所在的岛屿连接起来 for(int i=0; i<grid.size(); i++) { for(int j=0; j<grid[0].size(); j++) { if(grid[i][j] == 0) { set<int> st = findneibor(grid,i,j);//把上下左右邻居放入set去重 if(st.size() < 1) continue;//如果海洋格子周围没有格子不必计算 int twoIsland = 1;//填充这个格子,初始为1,这个变量记录合并岛屿后的面积 for(set<int>::iterator it=st.begin() ; it!=st.end(); it++) { twoIsland = twoIsland + mp[*it]; } res = max(res, twoIsland); } } } return res; } };
5、不同岛屿的数量
class Solution { public: vector<int> res; int numDistinctIslands(vector<vector<int>>& grid) { if(grid.size()==0){ return 0; } set<vector<pair<int,int>>> gridlength; vector<vector<int>> visited(grid.size(),vector<int>(grid[0].size())); for(int i=0;i<grid.size();++i){ for(int j=0;j<grid[0].size();++j){ if(grid[i][j]==1&&visited[i][j]==0){ caclu(grid,visited,i,j); sort(res.begin(),res.end()); int minrow=grid.si***column=grid[0].size(); //每块陆地的最小行号,最小列号 for(auto &item:res){ minrow=min(int(item/(grid[0].si***row); // 更新每块陆地的最小行号,最小列号 mincolumn=min(int(item%(grid[0].si***column); } vector<pair<int,int>> temp; for(auto &item:res){ int row=item/(grid[0].si***row; // 计算平移后的位置 int column=item%(grid[0].si***column; temp.push_back(move(pair<int,int>(row,column))); } gridlength.insert(move(temp)); } // else{ // //已被计算为岛屿 // continue; // } } } return gridlength.size(); } void caclu(const vector<vector<int>> &grid,vector<vector<int>> &visited,const int &i,const int &j){ res.clear(); dfs(grid,visited,i,j); } void dfs(const vector<vector<int>> &grid,vector<vector<int>> &visited,const int &i,const int &j){ if(i<0||j<0||i>=grid.size()||j>=grid[0].size()||visited[i][j]||grid[i][j]==0){ return; } visited[i][j]=1; res.push_back(i*grid[0].size()+j); // 岛屿空间 在 grid中的编号 dfs(grid,visited,i+1,j); dfs(grid,visited,i-1,j); dfs(grid,visited,i,j+1); dfs(grid,visited,i,j-1); } }; /* 作者:ZZZZHZZZZ 链接:https://leetcode-cn.com/problems/number-of-distinct-islands/solution/694bu-tong-dao-yu-de-shu-liang-by-zzzzhz-cnyy/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 */