【LeetCode每日一题:864.获取所有钥匙的最短路径~~~BFS+状态压缩+位运算】

题目描述

给定一个二维网格 grid ,其中:

‘.’ 代表一个空房间
‘#’ 代表一堵
‘@’ 是起点
小写字母代表钥匙
大写字母代表锁
我们从起点开始出发,一次移动是指向四个基本方向之一行走一个单位空间。我们不能在网格外面行走,也无法穿过一堵墙。如果途经一个钥匙,我们就把它捡起来。除非我们手里有对应的钥匙,否则无法通过锁。

假设 k 为 钥匙/锁 的个数,且满足 1 <= k <= 6,字母表中的前 k 个字母在网格中都有自己对应的一个小写和一个大写字母。换言之,每个锁有唯一对应的钥匙,每个钥匙也有唯一对应的锁。另外,代表钥匙和锁的字母互为大小写并按字母顺序排列。

返回获取所有钥匙所需要的移动的最少次数。如果无法获取所有钥匙,返回 -1 。

输入:grid = [“@.a.#”,“###.#”,“b.A.B”]
输出:8
解释:目标是获得所有钥匙,而不是打开所有锁。

提示:

m == grid.length
n == grid[i].length
1 <= m, n <= 30
grid[i][j] 只含有 ‘.’, ‘#’, ‘@’, ‘a’-‘f’ 以及 ‘A’-‘F’
钥匙的数目范围是 [1, 6]
每个钥匙都对应一个 不同 的字母
每个钥匙正好打开一个对应的锁

解题思路

  1. 因为钥匙的数目最多只有6把,因此我们可以通过一个int类型的整数来表示当前钥匙的收集情况。如果当前整数的第k位是1,表示收集到了该类型的钥匙;如果当前整数的第k位是0,表示没有收集到该类型的钥匙。
  2. 状态压缩钥匙的编号,按照顺序,:a钥匙对应的0,b钥匙对应的1,以此类推;
  3. 接下来就是通过位运算来堆对我们钥匙的状态进行记录:
    第k位置是否收集了钥匙:(state >> k) & 1,若返回 1 说明第 k 位为 1,当前持有种类编号为 k 的钥匙
    将第k位置更新为存在钥匙状态:state |= 1 << k,将 state 的第 k 位设置为 1,代表当前新收集到种类编号为 k 的钥匙
  4. 解决了上面俩个问题后,接下来就是BFS的过程了:
  • 找到起点位置,并将其进行入队,队列维护的是 (x,y,state)三元组状态(其中 (x,y)代表当前所在的棋盘位置,state 代表当前的钥匙收集情况) 同时统计整个棋盘所包含的钥匙数量 cnt,并使用哈希表记录到达每个状态所需要消耗的最小步数 step
  • 上右下左四个方向BFS,转移过程中需要注意题目的要求,遇到锁的时候,必须有对应钥匙才能通过;遇到钥匙时,需要更新对应的 state 再进行入队
  • 当 BFS 过程中遇到 state = (1 << cnt) - 1 时,代表所有钥匙均被收集完成,可结束搜索

实现代码

class Solution {
    static int N = 40, K = 7, INF = Integer.MAX_VALUE;
    static int[][][] dist = new int[N][N][1 << K];
    static int[][] dirs = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
    public int shortestPathAllKeys(String[] g) {
        int n = g.length, m = g[0].length(), cnt = 0;
        Deque<int[]> d = new LinkedList<>();
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                Arrays.fill(dist[i][j], INF);
                char c = g[i].charAt(j);
                if (c == '@') {
                    d.addLast(new int[]{i, j, 0});
                    dist[i][j][0] = 0;
                } else if (c >= 'a' && c <= 'z') cnt++;
            }
        }
        while (!d.isEmpty()) {
            int[] info = d.pollFirst();
            int x = info[0], y = info[1], cur = info[2], step = dist[x][y][cur];
            for (int[] di : dirs) {
                int nx = x + di[0], ny = y + di[1];
                if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
                char c = g[nx].charAt(ny);
                //遇到墙直接跳过
                if (c == '#') continue;
                //如果当前位置是锁,则需要判断当前锁之前是否收集过钥匙,能否打开它
                if ((c >= 'A' && c <= 'Z') && (cur >> (c - 'A') & 1) == 0) continue;
                int nowcur = cur;
                //如果遇到的是钥匙,则对齐进行收集
                if (c >= 'a' && c <= 'z') nowcur |= 1 << (c - 'a');
                //钥匙收集完毕直接放回结果
                //1 << cnt) - 1 cnt把钥匙都成功收集
                if (nowcur == (1 << cnt) - 1) return step + 1;
                //更新迭代
                if (step + 1 >= dist[nx][ny][nowcur]) continue;
                dist[nx][ny][nowcur] = step + 1;
                //加入队列
                d.addLast(new int[]{nx, ny, nowcur});
            }
        }
        return -1;
    }
}


运行结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

硕风和炜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值