news 2026/9/11 11:46:50

LeetCode 864:BFS与状态压缩解决钥匙收集最短路径问题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode 864:BFS与状态压缩解决钥匙收集最短路径问题

1. 问题背景与核心挑战

LeetCode 864题"获取所有钥匙的最短路径"是一个典型的图论与状态压缩结合的算法问题。给定一个二维网格,其中包含:

  • 起点 '@'
  • 墙壁 '#'
  • 空地 '.'
  • 小写字母表示钥匙(a-f)
  • 大写字母表示对应的锁(A-F)

玩家需要收集所有钥匙(每个字母钥匙只能开对应字母的锁),求从起点出发收集全部钥匙的最短路径步数。这个问题在现实中有诸多应用场景,比如:

  • 游戏中的关卡设计(如解谜游戏中的钥匙门机制)
  • 物流仓储中的权限区域访问
  • 网络安全中的多级认证路径优化

关键难点在于:路径搜索过程中需要动态记录已获取的钥匙状态,传统的BFS无法直接处理这种带有状态变化的路径搜索。

2. 算法选择与思路解析

2.1 为什么选择BFS+状态压缩

常规BFS适用于无权图的最短路径查找,但本题的特别之处在于:

  1. 路径有效性取决于钥匙获取状态
  2. 同一位置在不同钥匙状态下应被视为不同节点

状态压缩使用位运算来表示钥匙获取情况(Java中int类型足够表示a-f六把钥匙):

  • 每位代表一把钥匙(a=1<<0, b=1<<1,...)
  • 按位或操作记录新钥匙
  • 按位与操作检查是否有对应钥匙

2.2 三维状态表示法

我们需要扩展传统的(x,y)坐标到(x,y,keys)三维状态:

  • keys的二进制表示当前持有的钥匙
  • 例如:keys=0b000101表示持有a和c钥匙
  • 目标状态是持有所有钥匙(对于k把钥匙是(1<<k)-1)

3. Java实现详解

3.1 数据结构设计

class State { int x, y; int keys; State(int x, int y, int keys) { this.x = x; this.y = y; this.keys = keys; } // 重写equals和hashCode用于HashSet @Override public boolean equals(Object o) {...} @Override public int hashCode() {...} }

3.2 BFS核心框架

public int shortestPathAllKeys(String[] grid) { int m = grid.length, n = grid[0].length(); int allKeys = 0; Queue<State> queue = new LinkedList<>(); Set<State> visited = new HashSet<>(); // 初始化:找到起点和所有钥匙 for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { char c = grid[i].charAt(j); if (c == '@') { queue.offer(new State(i, j, 0)); visited.add(new State(i, j, 0)); } else if (c >= 'a' && c <= 'f') { allKeys |= (1 << (c - 'a')); } } } int[][] dirs = {{0,1},{1,0},{0,-1},{-1,0}}; int steps = 0; while (!queue.isEmpty()) { int size = queue.size(); for (int i = 0; i < size; i++) { State curr = queue.poll(); if (curr.keys == allKeys) return steps; for (int[] dir : dirs) { int x = curr.x + dir[0]; int y = curr.y + dir[1]; int keys = curr.keys; if (x < 0 || x >= m || y < 0 || y >= n) continue; char c = grid[x].charAt(y); if (c == '#') continue; // 墙 // 遇到锁且没有对应钥匙 if (c >= 'A' && c <= 'F' && (keys & (1 << (c - 'A'))) == 0) continue; // 遇到钥匙则更新状态 if (c >= 'a' && c <= 'f') keys |= (1 << (c - 'a')); State newState = new State(x, y, keys); if (!visited.contains(newState)) { visited.add(newState); queue.offer(newState); } } } steps++; } return -1; }

4. 关键优化与注意事项

4.1 状态判重优化

常规BFS使用二维坐标判重,但本题需要三维判重(x,y,keys)。实测发现:

  • 使用HashSet 存储已访问状态
  • 必须正确实现State类的equals和hashCode方法
  • 错误示例:仅比较x,y会导致错误剪枝

4.2 方向数组技巧

使用dirs数组表示四个方向比写四个if更简洁:

int[][] dirs = {{0,1},{1,0},{0,-1},{-1,0}}; // 右,下,左,上

4.3 钥匙数量计算

可以在初始化时统计钥匙数量:

int keyCount = 0; for (String row : grid) { for (char c : row.toCharArray()) { if (c >= 'a' && c <= 'f') keyCount++; } } allKeys = (1 << keyCount) - 1;

5. 复杂度分析与边界情况

5.1 时间复杂度

设网格大小为M×N,钥匙数量为K:

  • 状态总数:M×N×2^K
  • 每个状态处理O(1)(四个方向)
  • 总复杂度:O(M×N×2^K)

5.2 空间复杂度

主要消耗在visited集合:

  • O(M×N×2^K)

5.3 特殊测试用例

  1. 无钥匙情况:应返回0
  2. 钥匙被墙包围:返回-1
  3. 需要绕路获取钥匙顺序的情况
  4. 最大网格尺寸(30x30)和最多钥匙(6把)的性能测试

6. 实际应用扩展

这种BFS+状态压缩的技术还可用于:

  1. 多目标点最短路径问题(如同时收集多个物品)
  2. 动态障碍物场景(如随时间变化的迷宫)
  3. 多条件解锁的路径规划(如需要特定道具组合)

在游戏AI中,类似的算法可用于:

  • NPC的寻路决策
  • 自动解谜系统
  • 关卡难度测试

7. 常见错误与调试技巧

7.1 典型错误模式

  1. 忘记处理锁的检查条件:
// 错误:漏掉钥匙检查 if (c >= 'A' && c <= 'F') continue;
  1. 钥匙状态更新错误:
// 错误:直接修改curr.keys会影响其他方向 curr.keys |= (1 << (c - 'a'));
  1. 状态判重不完整:
// 错误:仅用坐标判重 visited.add(x + "," + y);

7.2 调试建议

  1. 打印关键状态:
System.out.println(x+","+y+" keys:"+Integer.toBinaryString(keys));
  1. 可视化小规模测试用例:
@a.A ... B.b
  1. 使用单元测试覆盖:
  • 无钥匙情况
  • 不可达情况
  • 需要特定顺序的情况

8. 算法变种与进阶

8.1 多玩家协作版本

假设可以有多人同时移动,求最短时间。这需要:

  • 状态扩展为(x1,y1,x2,y2,keys)
  • 协同移动策略

8.2 带权版本

如果不同格子有不同的移动代价(如沼泽减速),可以改用Dijkstra算法。

8.3 动态障碍物

如果障碍物会随时间变化,状态需要增加时间维度。

9. 性能优化实战

当网格较大(30x30)且钥匙较多(6把)时:

  1. 使用位运算优化状态处理
  2. 双向BFS搜索
  3. 启发式搜索(A*):
// 估算剩余步数=曼哈顿距离到最远钥匙 PriorityQueue<State> pq = new PriorityQueue<>(Comparator.comparingInt(s -> s.steps + heuristic(s)));

10. 工程实践建议

  1. 将网格解析与BFS逻辑分离
  2. 使用常量定义方向数组
  3. 添加详细的注释说明状态表示
  4. 编写完备的单元测试
  5. 对于游戏开发实际应用,可以考虑:
    • 预处理可通行区域
    • 分层路径规划
    • 结合导航网格(NavMesh)技术
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 11:46:19

纯PHP打造轻量级NAS:无数据库实现文件共享与WebDAV挂载

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 11:46:14

ESP32-S3 N16R8开发板入手指南:环境搭建与工程架构详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 11:44:44

如何3步追踪IP定位与手机号信息:GhostTrack新手实操教程

如何3步追踪IP定位与手机号信息&#xff1a;GhostTrack新手实操教程 【免费下载链接】GhostTrack Useful tool to track location or mobile number 项目地址: https://gitcode.com/GitHub_Trending/gh/GhostTrack 看到陌生IP访问你的服务&#xff0c;或收到未知号码发来…

作者头像 李华
网站建设 2026/9/11 11:44:25

编程入门指南:从Python到项目实战的完整路径

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华