适用于判断连通块个数、大小相关的题目;
求连通岛屿的数量及面积
1. 岛屿数量
给你一个由 '1'
(陆地)和 '0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:grid = [ ["1","1","1","1","0"], ["1","1","0","1","0"], ["1","1","0","0","0"], ["0","0","0","0","0"] ] 输出:1
class Solution {
public int numIslands(char[][] grid) {
// DFS
// 每次向下,水平进行搜索;搜到了就把所有的变为2(访问之后的);
// 遇到一个1就开始搜索一次
// 每搜索一次,岛屿数量加1
int count = 0;
for(int i=0; i< grid.length; i++) {
for(int j=0; j<grid[i].length; j++) {
if(grid[i][j] == '1') { // 当前元素为1,则生成岛屿
dfs(grid,i,j);
count ++;
}
}
}
return count;
}
// dfs 生成当成元素的岛屿
// 访问当前的 grid,x, y 为当前访问的坐标
void dfs(char[][] grid,int x,int y) {
if(x<0 || x >= grid.length || y < 0 || y >= grid[0].length) {
return ; // 超界直接return
}
if(grid[x][y] == '0' || grid[x][y] == '2'){ // 如果搜索到0或2了,也不继续下搜索了
return ;
}
if(grid[x][y] == '1') { // 等于1就把这个修改为2,然后继续往四周搜索
grid[x][y] = '2';
}
dfs(grid,x-1,y); // 往上走
dfs(grid,x,y+1);
dfs(grid,x,y-1);
dfs(grid,x+1,y);
return ;
}
}
2. 岛屿的最大面积
给你一个大小为 m x n
的二进制矩阵 grid
。
岛屿 是由一些相邻的 1
(代表土地) 构成的组合,这里的「相邻」要求两个 1
必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid
的四个边缘都被 0
(代表水)包围着。
岛屿的面积是岛上值为 1
的单元格的数目。
计算并返回 grid
中最大的岛屿面积。如果没有岛屿,则返回面积为 0
。
// 注: 也可以不维护全局变量,而是把dfs函数的返回值设置为int,通过dfs函数来传递每个岛屿的面积
class Solution {
int max = 0;
int size = 0; // 记录当前的面积
public int maxAreaOfIsland(int[][] grid) {
// dfs 出每个岛屿,并记录岛屿的面积
for(int i=0; i<grid.length ; i++) {
for(int j=0; j < grid[0].length; j++) {
if(grid[i][j] == 1) {
size = 0; // 更新size
dfs(grid,i,j);
max = Math.max(max,size);
}
}
}
return max;
}
//1. x , y 为当前遍历到的元素
void dfs(int[][]grid, int x, int y) {
// 2. 处理这个元素
// 2.1 元素越界 return
if(x < 0 || x >= grid.length || y < 0 || y >= grid[0].length) {
return ;
}
// 2.2 元素已经被处理完,或者元素为0;return
if(grid[x][y] != 1) {
return ;
}
// 2.3 标记元素,并继续向下搜索
grid[x][y] = 2;
size ++;
dfs(grid,x,y+1);
dfs(grid,x,y-1);
dfs(grid,x-1,y);
dfs(grid,x+1,y);
}
}
3. 水域大小
总结:主要能学到的是如何对八个方向进行遍历;以及在dfs中设置返回值。通过设置返回值来计算得到每一个水域的大小;
class Solution {
int[][] land;
int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0},{1,1},{1,-1},{-1,-1},{-1,1}}; // 八个方向
public int[] pondSizes(int[][] land) {
// 水域大小问题:值为0表示的是水域,然后如果值为0则表示水域;
// 思路:
// 1. 从网格图0出发,然后dfs访问八个方向的0;把这些0进行标记
// 2. dfs过程中记录池塘的大小;
// 3. dfs完之后放入数组中,再对数组进行排序即可
// 总结:与前面的题目相比就是在dfs的过程中从之前遍历四个方向,现在变成了遍历八个方向
this.land = land;
int m = land.length, n = land[0].length;
var ans = new ArrayList<Integer>();
for(int i=0; i<m ; i++) {
for(int j=0; j < n; j++) {
if(land[i][j] == 0) {
ans.add(dfs(i,j));
}
}
}
// 把list转换成int数组
var size = new int[ans.size()];
int i=0;
for(int x : ans)
size[i++] = x;
Arrays.sort(size);
return size;
}
// 返回值为当前岛屿的大小
public int dfs(int x, int y) { // 传入当前访问的元素下标
// 1. 越界则返回,或当前元素被访问过
if(x < 0 || x >= land.length || y<0 || y >= land
[0].length||land[x][y] != 0) {
return 0;
}
land[x][y] = 1; // 记录访问过了
int count = 1; // 记录元素数目,表示当前有岛屿数1
// 3. 访问8个方向;
for(var dir : dirs) {
count += dfs(x+dir[0], y+dir[1]); // dfs(i,j)从这个点得到的岛屿数,那以一个中心点,然后加上它8个方向可构成的岛屿数,不就得到了最后的岛屿数
}
return count;
}
}
- 4. 主题空间
「以扣会友」线下活动所在场地由若干主题空间与走廊组成,场地的地图记作由一维字符串型数组 grid
,字符串中仅包含 "0"~"5"
这 6 个字符。地图上每一个字符代表面积为 1 的区域,其中 "0"
表示走廊,其他字符表示主题空间。相同且连续(连续指上、下、左、右四个方向连接)的字符组成同一个主题空间。
假如整个 grid
区域的外侧均为走廊。请问,不与走廊直接相邻的主题空间的最大面积是多少?如果不存在这样的空间请返回 0
。
Notice: 在元素被访问后一般会设置一个标记,表示该位置已经被访问过;要注意在判断dfs结束条件时要加上如果访问到的是已经被遍历过的元素,那么就直接返回;
class Solution {
int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}};
boolean flag = true; // 标记是否与走廊相邻
char[][] gridCh;
char cur; // 记录开始访问的元素值
public int largestArea(String[] grid) {
// DFS: 同样是深搜;
// 如果搜到旁边不是相同的数字,则结束搜索,长度++;但是如果搜索到0了;或者搜索出界了,那这个空间就不考虑了
// 所以两个部分 1. 在旁边数字不相同时搜索停止;
// 2. 此时判断搜索停止时的数字,若为0或者则直接结束;搜出界也结束
// 先转化成一个二维数组方便操作
// 并记录空间的大小,如何设置空间的总大小为0呢
// 如何结束不也是个问题? 可以设置一个flag ; 如果为false表示这个空间不进行考虑了;
// 然后每次遍历一个主题空间时重新更新这个flag值。
// 四个方向进行搜索
// 1. 把这个string数组转成一个二维char数组
char[][] gridCh = new char[grid.length][grid[0].length()];
char[] chs;
for(int i=0; i<grid.length; i++) {
chs = grid[i].toCharArray();
gridCh[i] = chs;
}
this.gridCh = gridCh;
// for(int i=0; i<gridCh.length; i++) {
// for(int j=0; j<gridCh[0].length; j++) {
// System.out.print(gridCh[i][j]+" ");
// }
// System.out.println();
// }
// 2. 遍历这个二维char类型数组
int ans = 0; // 记录最大的空间面积
for(int i=0; i<gridCh.length; i++) {
for(int j=0; j<gridCh[0].length; j++) {
// if(gridCh[i][j] == '6') {
// continue;
// }
cur = gridCh[i][j];
int count = dfs(i,j); // 得到当前的一个区域面积
if(flag) { // 表明当前的考虑
ans = Math.max(count,ans);
}
flag = true; // 更新flag
}
}
return ans;
}
public int dfs(int x,int y) {
if(x < 0 || x >= gridCh.length || y<0 || y>=gridCh[0].length || gridCh[x][y] == '0') {
flag = false; // 超界 或着 遇到的元素是1 表明该主题空间与走廊相邻了
return 0;
}
// 没有超界,但是遇到的元素与当前的元素不同,则返回0
// 或者该元素已经被访问过
if(gridCh[x][y] != cur || gridCh[x][y] == '6') {
return 0;
}
// 这里修改为6之后,再次访问时间就会出现问题
gridCh[x][y] = '6'; // 把当前的元素标记为6表示访问过
int count = 1; //表明当前空间大小为1
for(var dir : dirs) {
count += dfs(x+dir[0],y+dir[1]);
}
return count;
}
}
5. 岛屿的周长
给定一个 row x col
的二维网格地图 grid
,其中:grid[i][j] = 1
表示陆地, grid[i][j] = 0
表示水域。
网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。
岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。
class Solution {
int[][] grid;
int m,n;
int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}};
public int islandPerimeter(int[][] grid) {
// 使用dfs深搜完成
// 每一个岛屿都会往四个方向进行搜素,如果搜素到的是水域或者是出界了,那么边数就+1;
// 最后结果即为这个岛屿的周长;
// 只有一个岛屿
this.grid = grid;
int m = grid.length;
int n = grid[0].length;
this.m = m; this.n = n;
int ans = 0;
for(int i=0; i<m; i++) {
for(int j=0; j<n; j++) {
if(grid[i][j] == 1) {
ans = dfs(i,j);
}
}
}
return ans;
}
// c => circumference 周长
public int dfs(int x, int y) {
if(x < 0 || x >=m || y<0 || y>= n || grid[x][y] == 0) {
// 出界 或 遇到水域了
return 1;
}
if(grid[x][y] == 2) {
return 0;
}
// 到这里表明遇到是普通的岛屿,标记已经访问
int c = 0;
grid[x][y] = 2; //表明被访问过
for(var dir : dirs) {
c += dfs(x + dir[0], y+dir[1]); // 这个dfs返回的是当前这个岛屿返回的当前周长了吧
}
return c;
}
}
其他不使用dfs或bfs的解决思路
6. 网格图中鱼的最大数目
本题可以转化为求岛屿的最大面积,没有区别的
class Solution {
// 记录四个方位,与把grid放到全局变量,代码会更加优雅
private final static int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
private int[][] grid;
public int findMaxFish(int[][] grid) {
// 执行任意次操作,就是可以在一个水域群中任意移动
// 相当于是在求所有水域中面积最大的那个
this.grid = grid;
int ans = 0;
for(int i=0; i<grid.length; i++) {
for(int j=0; j < grid[0].length; j++) {
ans = Math.max(ans, dfs(i,j));
}
}
return ans;
}
// dfs 表示从r,c出发可捕获的最大鱼数
public int dfs(int r, int c) {
if(r < 0 || r >= grid.length || c < 0 || c >= grid[0].length) {
return 0;
}
if(grid[r][c] == 0 ) { // 不能再继续移动了
return 0;
}
int sum = grid[r][c]; // 记录当前水域的值
grid[r][c] = 0; // 更新
for(var dir : dirs) {
sum += dfs(r+dir[0], c+dir[1]);
}
return sum;
}
}
如何判断当前元素是边缘元素
7. 边界着色
// 连通分量:即一个岛屿
class Solution {
int m;
int n;
int [][] grid;
int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}};
boolean[][] visited;
int color;
int originColor; // 记录最初的元素
public int[][] colorBorder(int[][] grid, int row, int col, int color) {
// step1: 确定连通分量
// step2: 将连通分量中在边框上的,或者四个方向上存在不是自己相同元素的进行标记,并且进行染色
this.grid = grid;
this.m = grid.length;
this.n = grid[0].length;
boolean[][] visited = new boolean[m][n];
for(int i=0; i<m; i++) {
for(int j=0; j<n; j++) {
visited[i][j] = false;
}
}
this.visited = visited;
this.color = color;
this.originColor= grid[row][col];
dfs(row,col);
return grid;
}
// 标记满足上述两个条件的块; 想使用一个visited数组来完成
public void dfs(int x, int y) {
visited[x][y] = true;
for(var dir : dirs) {
int x1 = x + dir[0]; int y1 = y + dir[1]; // 下一个元素
if(x1 < 0 || x1 >= m || y1 < 0 || y1 >= n) {
// 出界
grid[x][y] = color;
}else { // 没有出界
if(visited[x1][y1]) {
continue;
}
if(grid[x1][y1] == originColor){ // 颜色相同,一定要与最初的颜色进行对比,否则颜色可能会被修改
dfs(x1,y1);
}else {
grid[x][y] = color; // 颜色不同
}
}
}
}
}
寻找孤岛的思路
反向思路:孤岛即在湖中间的岛屿,这些岛屿不与边界的水域相连;我们要找到它可以从边界出发,进行dfs或bfs,把边界可达的岛屿都变成水域(淹城法),经过这样的操作之后,再遍历整个grid数组,其中剩下的岛屿元素就都是属于孤岛了。
8. 飞地的数量
给你一个大小为 m x n
的二进制矩阵 grid
,其中 0
表示一个海洋单元格、1
表示一个陆地单元格。
一次 移动 是指从一个陆地单元格走到另一个相邻(上、下、左、右)的陆地单元格或跨过 grid
的边界。
返回网格中 无法 在任意次数的移动中离开网格边界的陆地单元格的数量。
class Solution {
int[][] grid;
int m,n;
int[][] dirs = {{1,0},{-1,0},{0,1},{0,-1}};
public int numEnclaves(int[][] grid) {
// 从边缘进行 “淹城”
this.grid = grid;
this.m = grid.length;
this.n = grid[0].length;
for(int i=0; i < m; i++) {
if(grid[i][0] == 1) { // 第一列元素
dfs(i,0);
}
if(grid[i][n-1] == 1) { // 最后一列元素
dfs(i,n-1);
}
}
for(int j=0; j<n; j++) {
if(grid[0][j] == 1) { // 第一行元素
dfs(0,j);
}
if(grid[m-1][j] == 1) { // 最后一行元素
dfs(m-1,j);
}
}
int ans = 0;
for(int i=0; i<m; i++) {
for(int j=0; j<n; j++) {
if(grid[i][j] == 1) {
ans ++;
}
}
}
return ans;
}
// 将grid[x][y] 可达的都变成水域
public void dfs(int x, int y) {
if(x < 0 || x>=m || y < 0 || y>=n || grid[x][y] == 0) return;
grid[x][y] = 0;
for(var dir : dirs) {
dfs(x+dir[0],y+dir[1]);
}
}
}
9. 矩阵中移动的最大次数
给你一个下标从 0 开始、大小为 m x n
的矩阵 grid
,矩阵由若干 正 整数组成。
你可以从矩阵第一列中的 任一 单元格出发,按以下方式遍历 grid
:
- 从单元格
(row, col)
可以移动到(row - 1, col + 1)
、(row, col + 1)
和(row + 1, col + 1)
三个单元格中任一满足值 严格 大于当前单元格的单元格。
返回你在矩阵中能够 移动 的 最大 次数。
10. 统计封闭岛屿的数目
二维矩阵 grid
由 0
(土地)和 1
(水)组成。岛是由最大的4个方向连通的 0
组成的群,封闭岛是一个 完全
由1包围(左、上、右、下)的岛。
请返回 封闭岛屿 的数目。
class Solution {
int[][] grid;
int m;
int n;
int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}};
public int closedIsland(int[][] grid) {
/**
返回封闭岛屿的数目
逆向思维:可以从四边出发,即把四边的土地可以延申到的地方全部变成水域
此时剩下的就只有孤岛了
然后统计孤岛的数目即可;相当于是在统计岛屿数目
*/
this.grid = grid;
this.m = grid.length;
this.n = grid[0].length;
for(int i=0; i < m; i++) {
if(grid[i][0] == 0) dfs(i,0);
if(grid[i][n-1] == 0) dfs(i,n-1);
}
for(int j=0; j<n; j++) {
if(grid[0][j] == 0) dfs(0,j);
if(grid[m-1][j] == 0) dfs(m-1,j);
}
// 到这一步的时候此时grid只剩孤岛元素了;
int count = 0; //统计孤岛数目
for(int i=0; i< m; i++) {
for(int j =0 ; j<n; j++) {
if(grid[i][j] == 0){
dfs(i,j);
count ++;
}
}
}
return count;
}
// 将四周的土地变成1
public void dfs(int x, int y) {
if(x < 0 || x>= m || y< 0 || y>=n || grid[x][y] == 1) {
return ;
}
grid[x][y] = 1;
for(var dir : dirs) {
dfs(x + dir[0], y + dir[1]);
}
}
}
11. 被围绕的区域
结合 visited 数组来从边界访问
给你一个 m x n
的矩阵 board
,由若干字符 'X'
和 'O'
组成,捕获 所有 被围绕的区域:
- 连接:一个单元格与水平或垂直方向上相邻的单元格连接。
- 区域:连接所有
'O'
的单元格来形成一个区域。 - 围绕:如果您可以用
'X'
单元格 连接这个区域,并且区域中没有任何单元格位于board
边缘,则该区域被'X'
单元格围绕。
通过将输入矩阵 board
中的所有 'O'
替换为 'X'
来 捕获被围绕的区域。
class Solution {
char[][] board;
int m;
int n;
int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}};
boolean[][] visited;
public void solve(char[][] board) {
/**
被包围的区域就是无法到达边界的区域
反向思考:也就是从边界无法到达的区域
可以从边界的O出发,标记与其连接的O;最后遍历一遍grid,将未访问到的 O 全部变成X 即可
*/
// 初始化
this.board = board;
this.m = board.length;
this.n = board[0].length;
boolean[][] visited = new boolean[m][n];
for(int i=0; i<m; i++) {
for(int j=0; j<n; j++) {
visited[i][j] = false;
}
}
this.visited = visited;
for(int i=0; i < m; i++) {
if(board[i][0] == 'O') { // 第一列元素
dfs(i,0);
visited[i][0] = true;
}
if(board[i][n-1] == 'O') { // 最后一列元素
dfs(i,n-1);
visited[i][n-1] = true;
}
}
for(int j=0; j<n; j++) {
if(board[0][j] == 'O') { // 第一行元素
dfs(0,j);
visited[0][j] = true;
}
if(board[m-1][j] == 'O') { // 最后一行元素
dfs(m-1,j);
visited[m-1][j] = true;
}
}
for(int i=0; i< m; i++) {
for(int j=0; j<n; j++) {
if(board[i][j] == 'O' && !visited[i][j]) {
board[i][j] = 'X';
}
}
}
}
public void dfs(int x,int y) {
if(x < 0 || x>=m || y < 0 || y >= n || visited[x][y] || board[x][y] == 'X') {
return ;
}
visited[x][y] = true;
for(var dir : dirs) {
dfs(x+dir[0], y + dir[1]);
}
}
}
两个岛屿图下找到子岛屿 —— 叠图法
12. 统计子岛屿
给你两个 m x n
的二进制矩阵 grid1
和 grid2
,它们只包含 0
(表示水域)和 1
(表示陆地)。一个 岛屿 是由 四个方向 (水平或者竖直)上相邻的 1
组成的区域。任何矩阵以外的区域都视为水域。
如果 grid2
的一个岛屿,被 grid1
的一个岛屿 完全 包含,也就是说 grid2
中该岛屿的每一个格子都被 grid1
中同一个岛屿完全包含,那么我们称 grid2
中的这个岛屿为 子岛屿 。
请你返回 grid2
中 子岛屿 的 数目 。
示例 1:
输入:grid1 = [[1,1,1,0,0],[0,1,1,1,1],[0,0,0,0,0],[1,0,0,0,0],[1,1,0,1,1]], grid2 = [[1,1,1,0,0],[0,0,1,1,1],[0,1,0,0,0],[1,0,1,1,0],[0,1,0,1,0]] 输出:3 解释:如上图所示,左边为 grid1 ,右边为 grid2 。 grid2 中标红的 1 区域是子岛屿,总共有 3 个子岛屿。
class Solution {
int m;
int n;
int[][] grid;
boolean isSubLand = true;
int[][] dirs = {{1,0},{-1,0},{0,1},{0,-1}};
public int countSubIslands(int[][] grid1, int[][] grid2) {
/**
子岛屿:即grid1的岛屿中的一部分是grid2岛屿
返回grid2 中子岛屿的数目
叠图法:将grid2与grid1进行重叠即数值相加;
注意不可以直接相加,否则岛屿都会出现部分为1,部分为2的情况;
而是当grid2 与 grid1的相同位置同时为2时才可以相加
此时如果为grid1的子岛屿,那么此时grid2中该岛屿的元素应全部都是2;
如果部分为2,另一部分为1,则说明这不是子岛屿
而且这个1只可能是grid2自己留下来的
最后只用统计grid2中全为2的岛屿数目即可。
*/
this.m = grid1.length;
this.n = grid1[0].length;
// 1. 叠图:将两个grid数组相加
for(int i=0; i < m; i++) {
for(int j =0; j<n; j++) {
if(grid2[i][j] == grid1[i][j])
grid2[i][j] += grid1[i][j];
System.out.print(grid2[i][j] + " ");
}
System.out.println();
}
this.grid = grid2;
// 2. 求grid2中只含2的岛屿数目
// 碰到1了就说明当前统计的这个岛屿是一个部分子岛屿,不能被统计,设置一个flag标示
int count = 0;
for(int i=0; i < m; i++) {
for(int j =0; j<n; j++) {
if(grid[i][j] == 2) { // 当前元素是2
dfs(i,j);
if(isSubLand) {
count ++;
}
isSubLand = true; // 更新 标识
}
}
}
return count;
}
public void dfs(int x, int y) {
if(x < 0 || x>=m || y < 0 || y>=n || grid[x][y] == 0) return;
if(grid[x][y] == 1) { // 碰到1了
isSubLand = false;
return ;
}
grid[x][y] = 0 ;// 标示已经访问过的
for(var dir : dirs) {
dfs(x + dir[0], y + dir[1]);
}
}
}
// 下面的算法更快
// 思路与上面相同,不过是只有grid2为1时就相加,这样不会出现grid2中本来是0,却把grid1中的1加过来的情况
// class Solution {
// boolean find;
// public int countSubIslands(int[][] grid1, int[][] grid2) {
// int n = grid1.length,m = grid1[0].length;
// for(int i = 0; i < n; i++){
// for(int j = 0; j< m; j++){
// if(grid2[i][j] == 1) // 如果gird2中为1就相加
// grid2[i][j] += grid1[i][j];
// }
// }
// int ans =0;
// for(int i = 0; i < n;i++){
// for(int j = 0;j < m; j++){
// if(grid2[i][j] == 2){
// find = true;
// dfs(grid2,i,j);
// if(find) ans++;
// }
// }
// }
// return ans;
// }
// void dfs(int[][] grid,int i,int j){
// if(i < 0 || j < 0 || i == grid.length || j == grid[0].length) return;
// if(grid[i][j] == 1){ // 遇到1了表示不是
// find = false;
// return;
// }
// if(grid[i][j] == 0) return;
// grid[i][j] = 0;
// dfs(grid,i-1,j);
// dfs(grid,i,j+1);
// dfs(grid,i+1,j);
// dfs(grid,i,j-1);
// }
// }
13. 检查网格中是否存在有效路径
给你一个 m x n 的网格 grid
。网格里的每个单元都代表一条街道。grid[i][j]
的街道可以是:
- 1 表示连接左单元格和右单元格的街道。
- 2 表示连接上单元格和下单元格的街道。
- 3 表示连接左单元格和下单元格的街道。
- 4 表示连接右单元格和下单元格的街道。
- 5 表示连接左单元格和上单元格的街道。
- 6 表示连接右单元格和上单元格的街道。
你最开始从左上角的单元格 (0,0)
开始出发,网格中的「有效路径」是指从左上方的单元格 (0,0)
开始、一直到右下方的 (m-1,n-1)
结束的路径。该路径必须只沿着街道走。
注意:你 不能 变更街道。
如果网格中存在有效的路径,则返回 true
,否则返回 false
。
示例 1:
输入:grid = [[2,4,3],[6,5,2]] 输出:true 解释:如图所示,你可以从 (0, 0) 开始,访问网格中的所有单元格并到达 (m - 1, n - 1) 。