在问 · 算法题解

980. 不同路径 III

困难回溯回溯搜索力扣原题 ↗

题目描述

输入一个 m x n 的整数网格 grid,每格取以下四种值之一:1 是起点,2 是终点,0 是可以经过的空格,-1 是不能进入的障碍。起点和终点各恰好有一个,可以在网格的任何位置。

每一步可以向上、下、左、右移动一格。一条合法路径从起点出发、在终点结束,并且要把所有不是障碍的格子(含起点和终点)都恰好走过一次。

返回合法路径的条数。

示例 1
输入:grid = [[1,0,0,0],[0,0,0,0],[0,0,2,-1]]
输出:2

解释:绕过右下角的障碍,有两种走法能覆盖全部空格。

示例 2
输入:grid = [[1,0,0,0],[0,0,0,0],[0,0,0,2]]
输出:4

解释:没有障碍时,共有 4 种走法覆盖全部格子。

示例 3
输入:grid = [[0,1],[2,0]]
输出:0

解释:无论怎么走都无法把每个空格各走一次后停在终点。

数据范围

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

解题思路:回溯搜索

先统计非障碍格的数量 need(包括起点和终点),并找到起点。dfs(r, c, left) 表示站在 (r, c)、还有 left 个格子(含当前格)没走。到达终点时,只有 left == 1(只剩终点自己)才算一条合法路径。否则把当前格标为 -1 防止重走,向四个方向递归,返回前恢复为 0。

为什么正确

进入 dfs(r, c, left) 时,已经走过的格子都被标成 -1,且 left = need - 已走过的格子数。

复杂度分析

时间 O(3^(mn))(每格最多 3 个新方向),空间 O(mn) 的递归栈。

易错点

need 要把起点和终点都算上;到终点但还有空格没走时要返回 0,不能继续从终点往外走;回溯时一定要恢复格子。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def uniquePathsIII(self, grid: List[List[int]]) -> int:
        m, n = len(grid), len(grid[0])
        need = 0
        for i in range(m):
            for j in range(n):
                if grid[i][j] != -1:
                    need += 1
                if grid[i][j] == 1:
                    si, sj = i, j

        def dfs(r, c, left):
            if grid[r][c] == 2:
                return 1 if left == 1 else 0
            grid[r][c] = -1
            total = 0
            for nr, nc in ((r + 1, c), (r - 1, c), (r, c + 1), (r, c - 1)):
                if 0 <= nr < m and 0 <= nc < n and grid[nr][nc] != -1:
                    total += dfs(nr, nc, left - 1)
            grid[r][c] = 0
            return total

        return dfs(si, sj, need)

载入编辑器,自己跑一遍 →

其他解法

回溯直接枚举所有路径,代码简单,空间只需递归栈;状态压缩记忆化合并了重复状态,是“哈密顿路径计数”的通用做法,但需要额外的哈希表。

解法 2:状态压缩记忆化搜索 时间 O(mn · 2^(mn)),空间 O(mn · 2^(mn))

把格子编号为 r·n + c,用一个二进制数 mask 记录已经走过的格子。状态 (cell, mask) 表示当前站在 cell、已访问集合为 mask 时,走到终点的路径数。到达终点时,只有 mask 等于所有非障碍格的集合才算一条路径。转移时枚举四个方向上没访问过的格子,结果用哈希表记忆化,相同状态只计算一次。

当不同路线到达相同的位置和访问集合时可以复用结果,体现了状态压缩 DP 的思想。状态数理论上很大,本题格子不超过 20 个才可行,而且实际运行不一定比剪枝良好的回溯快。

from functools import lru_cache

class Solution:
    def uniquePathsIII(self, grid: List[List[int]]) -> int:
        m, n = len(grid), len(grid[0])
        full = 0
        for i in range(m):
            for j in range(n):
                if grid[i][j] != -1:
                    full |= 1 << (i * n + j)
                if grid[i][j] == 1:
                    start = i * n + j

        @lru_cache(maxsize=None)
        def dp(cell, mask):
            r, c = divmod(cell, n)
            if grid[r][c] == 2:
                return 1 if mask == full else 0
            total = 0
            for nr, nc in ((r + 1, c), (r - 1, c), (r, c + 1), (r, c - 1)):
                if 0 <= nr < m and 0 <= nc < n and grid[nr][nc] != -1:
                    nxt = nr * n + nc
                    if not mask >> nxt & 1:
                        total += dp(nxt, mask | 1 << nxt)
            return total

        return dp(start, 1 << start)

回溯 专题的其他题目

查看 回溯 全部题目 →