Different Paths III -- 不同路径 III

980. 不同路径 III - 力扣(LeetCode)icon-default.png?t=N6B9https://leetcode.cn/problems/unique-paths-iii/description/

On a two-dimensional grid, there are four types of squares:

1 indicates the starting square. And there's only one starting grid.
2 indicates the end square, and there is only one end square.
0 represents the empty squares we can walk through.
-1 is an obstacle we can't cross.
Returns the number of different paths from the start square to the end square when walking in four directions (up, down, left, and right).

Each barrier-free square must be passed once, but the same square cannot be passed repeatedly in a path.

在二维网格 grid 上,有 4 种类型的方格:

  • 1 表示起始方格。且只有一个起始方格。
  • 2 表示结束方格,且只有一个结束方格。
  • 0 表示我们可以走过的空方格。
  • -1 表示我们无法跨越的障碍。

返回在四个方向(上、下、左、右)上行走时,从起始方格到结束方格的不同路径的数目

每一个无障碍方格都要通过一次,但是一条路径中不能重复通过同一个方格

示例 1:

输入:[[1,0,0,0],[0,0,0,0],[0,0,2,-1]]
输出:2
解释:我们有以下两条路径:
1. (0,0),(0,1),(0,2),(0,3),(1,3),(1,2),(1,1),(1,0),(2,0),(2,1),(2,2)
2. (0,0),(1,0),(2,0),(2,1),(1,1),(0,1),(0,2),(0,3),(1,3),(1,2),(2,2)

示例 2:

输入:[[1,0,0,0],[0,0,0,0],[0,0,0,2]]
输出:4
解释:我们有以下四条路径: 
1. (0,0),(0,1),(0,2),(0,3),(1,3),(1,2),(1,1),(1,0),(2,0),(2,1),(2,2),(2,3)
2. (0,0),(0,1),(1,1),(1,0),(2,0),(2,1),(2,2),(1,2),(0,2),(0,3),(1,3),(2,3)
3. (0,0),(1,0),(2,0),(2,1),(2,2),(1,2),(1,1),(0,1),(0,2),(0,3),(1,3),(2,3)
4. (0,0),(1,0),(2,0),(2,1),(1,1),(0,1),(0,2),(0,3),(1,3),(1,2),(2,2),(2,3)

示例 3:

输入:[[0,1],[2,0]]
输出:0
解释:
没有一条路能完全穿过每一个空的方格一次。
请注意,起始和结束方格可以位于网格中的任意位置。

提示:

  • 1 <= grid.length * grid[0].length <= 20

Method 1: Backtrack

If there are n zeros in the matrix, then a qualified path is a path of length (n+1) that starts with 1 and ends with 2, does not pass through −1, and passes through each point only once. To find out all the qualified paths, we can use the backtracking method to define the function dfs, which represents the number of paths starting from the point (i,j) and going through n points to the end in the current grid state. When a point is reached, if the current point is the end point and (n+1) points have been passed, then a qualified path is formed, otherwise it is not formed. If the current point is not the end point, the current point is marked as −1, indicating that the path cannot pass this point in the future, and then continue to expand in four directions at this point, if it does not exceed the boundary and the value of the next point is 0 or 2, it means that the path can continue to expand. After exploring the four directions, you need to change the value of the current point to the original value. The sum of the qualified paths in the four directions is the number of qualified paths in the current state. The final return is the number of paths the grid needs to pass through (n+1) points from the starting point in its initial state.

方法一:回溯

思路:按照要求,假设矩阵中有 n 个 0,那么一条合格的路径,是长度为 (n+1),由 1 起始,结束于 2,不经过 −1,且每个点只经过一次的路径。要求出所有的合格的路径,可以采用回溯法,定义函数 dfs,表示当前 grid 状态下,从点 (i,j) 出发,还要经过 n 个点,走到终点的路径条数。到达一个点时,如果当前的点为终点,且已经经过了 (n+1) 个点,那么就构成了一条合格的路径,否则就不构成。如果当前的点不为终点,则将当前的点标记为 −1,表示这条路径以后不能再经过这个点,然后继续在这个点往四个方向扩展,如果不超过边界且下一个点的值为 0 或者 2,则表示这条路径可以继续扩展。探测完四个方向后,需要将当前的点的值改为原来的值。将四个方向的合格路径求和,即为当前状态下合格路径的条数。最终需要返回的是,grid 在初始状态下,从起点出发,需要经过 (n+1) 个点的路径条数。


class Solution {
    static int[][] dirs = {
   
   {-1, 0}, {1, 0}, {0, -1}, {0, 1}};

    public int uniquePathsIII(int[][] grid) {
        int r = grid.length, c = grid[0].length;
        int si = 0, sj = 0, n = 0;
        
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                if (grid[i][j] == 0) {
                    n++;
                } else if (grid[i][j] == 1) {
                    n++;
                    si = i;
                    sj = j;
                }
            }
        }
        return dfs(grid, si, sj, n);
    }

    public int dfs(int[][] grid, int i, int j, int n) {
        if (grid[i][j] == 2) {
            return n == 0 ? 1 : 0;
        }
        int r = grid.length, c = grid[0].length;
        int t = grid[i][j];
        grid[i][j] = -1;
        int res = 0;
        for (int[] dir : dirs) {
            int ni = i + dir[0], nj = j + dir[1];
            if (ni >= 0 && ni < r && nj >= 0 && nj < c && (grid[ni][nj] == 0 || grid[ni][nj] == 2)) {
                res += dfs(grid, ni, nj, n - 1);
            }
        }
        grid[i][j] = t;
        return res;
    }
}

Method 2: Memorized search + state compression

Idea: Method 1 of the backtracking function, even in the case of i,j,n are the same, the return value of the function will be different, because the path through the point is different, resulting in the current situation grid\textit{grid}grid state is also different. Therefore, we can put the state of the grid into the input parameters of the function, thereby reducing the time complexity by memorizing the search.

A binary number st is used to represent the point that the path has not passed through (the initial state is all the points with a value of 0 and the end point), and the coordinates of the point need to correspond to the bits of the binary number. Define the function dp, the input parameters are the current coordinate i,j and the binary set st of the points passed through, and the return value is the number of paths starting from the point (i,j), passing through the set of points represented by st, and finally reaching the end point. If the current point is the end point and there are no unpassed points left, then the current return value is 1, otherwise 0. If the current point is not the end point, you need to explore the four directions, if the next point is within the boundary and has not yet passed (judged by the bit-sum operation), you need to go to that point and remove the coordinates of that point from the set of unpassed points (determined by the bit-sum operation). The sum of the qualified paths in the four directions is the number of qualified paths in the current state. The final thing that needs to be returned is the number of paths from the starting point for the set of points passed to all points with a value of 0 and the end point. A memorized search is used during the function call, and each state is calculated at most once.

方法二:记忆化搜索 + 状态压缩
思路:方法一的回溯函数,即使在 i,j,n 都相同的情况下,函数的返回值也会不同,因为路径经过的点不同,导致当前情况下 grid\textit{grid}grid 的状态也不同。因此,我们可以将 grid 的状态放入函数的输入参数,从而用记忆化搜索来降低时间复杂度。

用一个二进制数字 st 来表示路径还未经过的点(初始状态下为所有值为 0 的点和终点),点的坐标需要和二进制数字的位一一对应。定义函数 dp,输入参数为当前坐标 i,j 和为经过的点的二进制集合 st,返回值即为从点 (i,j) 出发,经过 st 代表的点的集合,最终到达终点的路径的条数。如果当前点为终点且剩下没有未经过的点,那么当前的返回值即为 1,否则为 0。如果当前的点不为终点,则需要探索四个方向,如果接下来的点在边界内且还未经过(用按位和操作判断),则需要走到那个点并且将那个点的坐标从未经过的点的集合中去掉(用按位异或操作)。将四个方向的合格路径求和,即为当前状态下合格路径的条数。最终需要返回的是,从起点出发,为经过的点的集合为所有值为 0 的点和终点的路径条数。函数调用过程中采用了记忆化搜索,每个状态最多只会被计算一次。

class Solution {
    static int[][] dirs = {
   
   {-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    Map<Integer, Integer> memo = new HashMap<Integer, Integer>();

    public int uniquePathsIII(int[][] grid) {
        int r = grid.length, c = grid[0].length;
        int si = 0, sj = 0, st = 0;
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                if (grid[i][j] == 0 || grid[i][j] == 2) {
                    st |= 1 << (i * c + j);
                } else if (grid[i][j] == 1) {
                    si = i;
                    sj = j;
                }
            }
        }
        return dp(grid, si, sj, st);
    }

    public int dp(int[][] grid, int i, int j, int st) {
        if (grid[i][j] == 2) {
            return st == 0 ? 1 : 0;
        }
        int r = grid.length, c = grid[0].length;
        int key = ((i * c + j) << (r * c)) + st;
        if (!memo.containsKey(key)) {
            int res = 0;
            for (int[] dir : dirs) {
                int ni = i + dir[0], nj = j + dir[1];
                if (ni >= 0 && ni < r && nj >= 0 && nj < c && (st & (1 << (ni * c + nj))) > 0) {
                    res += dp(grid, ni, nj, st ^ (1 << (ni * c + nj)));
                }
            }
            memo.put(key, res);
        }
        return memo.get(key);
    }
}

猜你喜欢

转载自blog.csdn.net/m0_72572822/article/details/132097187