leetcode
leetcode 2251 ~ 2300
网格图中最少访问的格子数

网格图中最少访问的格子数

难度:

标签:

题目描述

You are given a 0-indexed m x n integer matrix grid. Your initial position is at the top-left cell (0, 0).

Starting from the cell (i, j), you can move to one of the following cells:

  • Cells (i, k) with j < k <= grid[i][j] + j (rightward movement), or
  • Cells (k, j) with i < k <= grid[i][j] + i (downward movement).

Return the minimum number of cells you need to visit to reach the bottom-right cell (m - 1, n - 1). If there is no valid path, return -1.

 

Example 1:

Input: grid = [[3,4,2,1],[4,2,3,1],[2,1,0,0],[2,4,0,0]]
Output: 4
Explanation: The image above shows one of the paths that visits exactly 4 cells.

Example 2:

Input: grid = [[3,4,2,1],[4,2,1,1],[2,1,1,0],[3,4,1,0]]
Output: 3
Explanation: The image above shows one of the paths that visits exactly 3 cells.

Example 3:

Input: grid = [[2,1,0],[1,0,0]]
Output: -1
Explanation: It can be proven that no path exists.

 

Constraints:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 105
  • 1 <= m * n <= 105
  • 0 <= grid[i][j] < m * n
  • grid[m - 1][n - 1] == 0

代码结果

运行时间: 662 ms, 内存: 47.4 MB


/*
 * 思路:
 * 使用广度优先搜索(BFS)算法,从左上角开始遍历每一个格子,记录到达每个格子所需的最少步数。
 * 使用Java Stream API来处理队列操作。
 */
import java.util.*;
import java.util.stream.Collectors;

public class SolutionStream {
    public int minMoves(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int[][] directions = {{0, 1}, {1, 0}};
        boolean[][] visited = new boolean[m][n];
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[]{0, 0});
        visited[0][0] = true;
        int moves = 0;

        while (!queue.isEmpty()) {
            List<int[]> currentLevel = queue.stream().collect(Collectors.toList());
            queue.clear();
            for (int[] current : currentLevel) {
                int x = current[0];
                int y = current[1];
                if (x == m - 1 && y == n - 1) return moves;

                for (int[] direction : directions) {
                    for (int step = 1; step <= grid[x][y]; step++) {
                        int nx = x + direction[0] * step;
                        int ny = y + direction[1] * step;
                        if (nx >= m || ny >= n) break;
                        if (!visited[nx][ny]) {
                            queue.offer(new int[]{nx, ny});
                            visited[nx][ny] = true;
                        }
                    }
                }
            }
            moves++;
        }
        return -1;
    }
}

解释

方法:

这个题解采用的是动态规划的思想,结合一种特殊的数据结构来优化搜索过程。从网格的右下角开始反向思考到达左上角的最小步数,使用两个列表来维护可能的移动路径和步数。对于每一个格子,计算向左和向上移动的最小步数,如果这个步数小于当前记录的步数,则更新。这种方法通过从右下到左上的方式,逐步缩减搜索空间,并在过程中不断更新每个格子到达终点的最小步数。

时间复杂度:

O(m*n*log(n))

空间复杂度:

O(m*n)

代码细节讲解

🦆
题解中提到从右下角开始反向思考到达左上角的最小步数,这种反向思考的方法相比从左上角到右下角有什么优势?
从右下角开始反向思考到达左上角的最小步数的优势在于可以直接处理目标格子(右下角)的初始化问题,并从结束点开始逐步向起始点推进,这样的计算逻辑使得每一步都建立在已经计算过的结果之上。这种反向方式有助于减少中间状态的存储和处理,特别是在有些情况下,从终点到起点的路径可能比从起点到终点更容易处理或者更直观。此外,对于一些特定类型的问题,这种方式可能会更自然地适应问题的边界条件和限制。
🦆
在题解中使用的两个列表来维护可能的移动路径和步数,请问这些列表具体是如何更新和维护的?
在题解中,维护的两个列表分别是列状态列表(col_st)和行状态列表(rst)。列状态列表用于存储每列的状态,行状态列表用于存储当前处理行的状态。这些列表在算法中通过动态地添加和删除元素来维护。具体地,对于每一列或行,在进行动态规划计算时,会根据该列或行的特定格子和之前计算的状态,来决定是否更新步数。更新操作涉及到根据当前格子的行动成本以及之前的状态来计算新的最小步数,并将这个状态添加到列表中。同时,为了维持状态的最优性,会移除那些不再可能成为最优解的旧状态。这种动态的更新保证了每一步的计算都是基于最新的最优状态进行的。
🦆
题解中提到了使用动态规划的思想,能否详细解释这里的动态规划状态是如何定义的?
在题解中使用的动态规划的状态定义为每个格子到达终点(右下角)的最小步数。具体来说,状态可以表示为一个二元组 (i, p),其中 i 表示当前考虑的列或行的索引,p 表示从当前位置到终点的最小步数。动态规划的转移过程则是基于从一个格子到其相邻格子(向上或向左)的移动成本,来更新这些状态。对于每个格子,算法会考虑从当前格子移动到上方或左侧格子的成本,然后根据这些成本以及从这些相邻格子到终点的已知最小步数,来更新当前格子到终点的最小步数。这样的状态转移保证了每一步的更新都是向着减少总步数的方向进行的。

相关问题