最近在刷LeetCode时,我遇到了两道看似风马牛不相及的题目:螺旋矩阵III(885题)和最短路径(如迷宫问题、岛屿最短距离等)。但深入思考后我发现,它们在底层逻辑上有着惊人的一致性------都依赖于一套方向控制系统,却在步长策略和边界约束上走向了截然不同的分支。这篇文章就从方向控制的视角,带你拆解它们的共性与差异。
一、共同的内核:方向数组
无论是螺旋矩阵还是最短路径,第一步都是定义如何移动。在二维网格中,我们通常用四个基本方向:右、下、左、上。对应的坐标变化为:
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
这个数组就像一个发动机的曲轴,驱动着角色在网格中前进。螺旋矩阵用它来"绕圈",最短路径用它来"探索"。
二、螺旋增步遍历:机械式的精密舞步
螺旋矩阵III的任务是从一个起点出发,按照顺时针方向,以递增的步长遍历整个矩阵。它的核心规律是:**向右1步,向下1步,向左2步,向上2步,向右3步,向下3步......** 步长每两个方向增加1。
这段代码完美体现了这个规律:
def spiralMatrixIII(rows, cols, rStart, cStart):
total = rows * cols
result = [[rStart, cStart]]
if total == 1:
return result
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
steps = 1
dir_idx = 0
r, c = rStart, cStart
while len(result) < total:
for _ in range(2): # 每两个方向步长相同
dr, dc = directions[dir_idx % 4]
for _ in range(steps): # 在当前方向上走 steps 步
r += dr
c += dc
if 0 <= r < rows and 0 <= c < cols:
result.append([r, c])
if len(result) == total:
return result
dir_idx += 1 # 切换方向
steps += 1 # 步长增加
return result
这里的关键设计是 for _ in range(2) 与 steps += 1 的分离。前者控制"每几个方向共享同一步长",后者控制步长增长的节奏。你可以把 range(2) 改成 range(1) 或 range(3),就能得到完全不同的螺旋模式------这证明了步长策略与方向切换是解耦的。
螺旋矩阵的特点在于:它不关心是否重复经过某个格子,只关心是否收集够了所有格子。它像一个设定好程序的机器人,严格按照预设的步长和方向行走,超出边界就跳过,直到任务完成。它不需要记忆,也不需要回头。
三、最短路径:有记忆的探索
最短路径问题则完全不同。它的目标是找到从起点到终点的最短路线,通常需要避开障碍物,并且绝对不能走回头路。为什么不能走回头路?因为一旦允许重复访问,算法就会在两个格子之间来回振荡,陷入死循环。
经典的广度优先搜索(BFS)解决了这个问题:
from collections import deque
def shortestPath(grid, start, end):
rows, cols = len(grid), len(grid[0])
visited = set()
queue = deque([(start[0], start[1], 0)]) # (r, c, distance)
visited.add(start)
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
while queue:
r, c, dist = queue.popleft()
if (r, c) == end:
return dist
for dr, dc in directions:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] != 1 and (nr, nc) not in visited:
visited.add((nr, nc))
queue.append((nr, nc, dist + 1))
return -1 # 无法到达
这里最关键的数据结构是 visited 集合。它记录了所有已经访问过的格子,防止算法走回头路。BFS 从起点开始,像水面波纹一样一层层向外扩散,第一次到达终点时的距离就是最短路径。
四、核心对比:一张表看清
| 维度 | 螺旋增步遍历 | 最短路径(BFS) |
|---|---|---|
| 目标 | 遍历所有格子 | 找到最短路径 |
| 步长 | 递增(1,1,2,2,...) | 固定(每次1步) |
| 是否允许重复访问 | 允许(但不关注) | 禁止(通过 visited 集合) |
| 终止条件 | 收集满所有格子 | 到达目标点 |
| 核心数据结构 | 方向数组 + 步长计数器 | 队列 + visited 集合 |
| 典型应用 | 螺旋矩阵、蛇形遍历 | 迷宫寻路、网络路由 |
五、从机械到算法的联想
我在学习螺旋矩阵时,总联想到大众EA827化油器的机械结构。方向数组就像凸轮轴上的四个凸起,按右→下→左→上的顺序推动摇臂;for _ in range(2) 就像曲轴每转两圈完成一个工作循环;而 steps 递增则像是步进电机加大节气门开度,喷油量逐渐增加。这种跨学科的类比让我对算法有了更立体的理解。
最短路径则更像是一个智能导航系统:它需要实时感知周围环境(障碍物),记住走过的路(visited),并选择最优的路线(BFS的层次扩展)。
六、写在最后
方向数组是这两类问题的共同基因,但不同的步长策略和边界约束造就了迥异的算法形态。理解了这个底层逻辑,你就能在面对新的方向控制问题时,快速定位它属于"螺旋派"还是"寻路派",并选择合适的工具。
下次当你看到一个需要"上下左右"移动的题目时,不妨先问自己三个问题:
- 步长是固定的还是变化的?
- 允不允许重复访问?
- 目标是遍历还是寻路?
答案自然会浮现。
希望这篇文章能帮助你建立自己的算法知识图谱。如果你也有类似的跨学科类比,欢迎在评论区分享。让我们一起,把代码变成看得见的风景。