SWE · Coding

网格从首行到底行:可达、全部简单路径、权重最短路

三问的目标不同,全部路径输出不能套BFS全局visited。

题目重点:三问的目标不同,全部路径输出不能套BFS全局visited。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 0格可走、1格障碍,四邻接;起点为首行任一可走格,第一次进入底行即结束。矩阵矩形。
  • 权重为非负格子进入费用,包含起点;所有路径不重复格子。若允许仅向下、费用仅边或必须指定起点须调整。

解题路线

  1. 可达单独用多源BFS,起点同时入队,O(RC)。代码的all_paths为教学生成器,用当前路径seen回溯,不能用永久visited压掉另一合法路径。
  2. weighted把所有首行起点以自身费用初始化Dijkstra,弹出首个底行节点即全局最优。非负费用保证settle正确,零费用环不需重复走。
  3. 路径数可能指数甚至更快增长,generator只减少同时输出存储,不减少枚举时间。生产接口要限制maxPaths/超时并标明截断,不能偷偷裁剪后称全部。

手动走一遍

1行网格每个可走格即一条长度1路径,最短费用取首行最低格权重。2×2全通网格到不同底格有多个简单路径,同节点可处于不同搜索分支。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

from heapq import heappush,heappop

def neighbors(grid,r,c):
    for x,y in ((r-1,c),(r+1,c),(r,c-1),(r,c+1)):
        if 0<=x<len(grid) and 0<=y<len(grid[0]) and grid[x][y]==0:yield x,y

def all_paths(grid):
    if not grid or not grid[0]:return
    def dfs(r,c,path,seen):
        if r==len(grid)-1:yield tuple(path);return
        for cell in neighbors(grid,r,c):
            if cell not in seen:
                seen.add(cell);path.append(cell)
                yield from dfs(*cell,path,seen)
                path.pop();seen.remove(cell)
    for c in range(len(grid[0])):
        if grid[0][c]==0:yield from dfs(0,c,[(0,c)],{(0,c)})

def shortest(grid,weights):
    if not grid or not grid[0]:return None
    if len(weights)!=len(grid) or any(len(row)!=len(grid[0]) for row in grid+weights):raise ValueError("shape")
    if any(w<0 for row in weights for w in row):raise ValueError("nonnegative weights")
    best={};heap=[]
    for c in range(len(grid[0])):
        if grid[0][c]==0:best[(0,c)]=weights[0][c];heappush(heap,(weights[0][c],0,c))
    while heap:
        cost,r,c=heappop(heap)
        if best[(r,c)]!=cost:continue
        if r==len(grid)-1:return cost
        for x,y in neighbors(grid,r,c):
            new=cost+weights[x][y]
            if new<best.get((x,y),float('inf')):best[(x,y)]=new;heappush(heap,(new,x,y))
    return None

复杂度

可达O(V+E);全部路径O(探索路径前缀总量+输出字符量),stack O(V);Dijkstra网格O(VlogV)时间/O(V)空间。

易错点与追问

容易失分的边界

  • 所有路径用全局visited会漏结果。
  • 负权不能用Dijkstra,允许负环时最短路可能不存在。

继续深挖

  • 仅允许向下/左右时可否改DAG DP,左右仍可能产生环吗?
  • 要最短路径本身,如何存多源parent且区分起点?
✓已复制微信号!