【每日一题DAY23】LC864获取所有钥匙的最短路径 |状态压缩 BFS

18 篇文章 0 订阅
12 篇文章 0 订阅

状态压缩

  • 状态压缩:一个维度能表示所有物品的状态情况
  • n件物品,每个物品有2个状态(状态1和状态2),那么可以用n位2进制表示物品的状态
    • 第k位为1时,表示第k件物品的状态为状态1
    • 第k位为0时,表示第k件物品的状态为状态2
  • 比如
    • LC864中可用一个int类型二进制代表当前收集到钥匙的情况
    • 背包问题中每件物品放或不放的情况
  • 状态检测:(state >> k) & 1
    • 返回1说明第k位为1
    • 返回0说明第k位为0
  • 状态更新:state |= 1 << k
    • state 的第 k位设置为 1

获取所有钥匙的最短路径【LC864】

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

  • ‘.’ 代表一个空房间
  • ‘#’ 代表一堵
  • ‘@’ 是起点
  • 小写字母代表钥匙
  • 大写字母代表锁

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

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

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

You are given an m x n grid grid where:

  • '.' is an empty cell.
  • '#' is a wall.
  • '@' is the starting point.
  • Lowercase letters represent keys.
  • Uppercase letters represent locks.

You start at the starting point and one move consists of walking one space in one of the four cardinal directions. You cannot walk outside the grid, or walk into a wall.

If you walk over a key, you can pick it up and you cannot walk over a lock unless you have its corresponding key.

For some 1 <= k <= 6, there is exactly one lowercase and one uppercase letter of the first k letters of the English alphabet in the grid. This means that there is exactly one key for each lock, and one lock for each key; and also that the letters used to represent the keys and locks were chosen in the same order as the English alphabet.

Return the lowest number of moves to acquire all keys. If it is impossible, return -1.

写了快两个小时的回溯没写出来,最后看的三叶姐的题解。
感觉可以看做四叉树,首先找到树的根节点(起点@),然后压入队列,每个节点有四种方向,判断位置是否合法,然后根据元素进行相应的操作。每个位置可能重复到达,因此需要记录每个位置对应的步数。第一次找到所有钥匙时,返回前一个位置的步数+1

  • 思路:

    • 使用状态压缩记录当前收集到钥匙的情况state,便于使用位运算进行钥匙检测和更新钥匙收集状态
    • 起始遍历一次棋盘
      • 找到起点位置,并将其进行入队,队列维护的是 (x,y,state)三元组状态(其中 (x,y)代表当前所在的棋盘位置,state 代表当前的钥匙收集情况),
      • 同时统计整个棋盘所包含的钥匙数量 cnt
      • 并使用 数组/哈希表 记录到达每个位置所需要消耗的最小步数 step
    • 然后进行四联通方向的BFS,从一个位置搜寻它的四个方向,如果能够到达某一个位置,并且步数比先前到达该位置还要小,那么将其的三元组加入队列的末端,再次搜寻【BFS可以保证第一次找到全部钥匙时一定是最小步数】
    • BFS 过程中遇到 state = (1 << cnt) - 1 时,代表所有钥匙均被收集完成,可结束搜索
  • 状态压缩:使用一个int类型二进制state代表当前收集到钥匙的情况

    • 第k位为1时,表示第k个钥匙已被收集
    • 第k位为0时,表示第k个钥匙未被收集
  • 状态检测:(state >> k) & 1

    • 返回1说明第k位为1
    • 返回0说明第k位为0
  • 状态更新:state |= 1 << k

    • state 的第 k位设置为 1,代表当前新收集到种类编号为 k 的钥匙
  • 四联通方向BFS的搜索过程

    • 边界外:跳过
    • 遇到锁时
      • 没有对应钥匙:跳过
      • 有钥匙时
        • 步数小于当前位置的最小步数:更新步数,入队
        • 步数大于当前位置的最小步数:跳过
    • 遇到钥匙时,需要更新对应的 state
      • 步数小于当前位置的最小步数:更新步数,入队
      • 步数大于当前位置的最小步数:跳过
  • 代码

    class Solution {
        static int N = 35, K = 10, INF = 0x3f3f3f3f;
        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 ArrayDeque<>();
            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 ncur = cur;
                    if (c >= 'a' && c <= 'z') ncur |= 1 << (c - 'a');
                    if (ncur == (1 << cnt) - 1) return step + 1;
                    if (step + 1 >= dist[nx][ny][ncur]) continue;
                    dist[nx][ny][ncur] = step + 1;
                    d.addLast(new int[]{nx, ny, ncur});
                }
            }
            return -1;
        }
    }
    
    作者:宫水三叶
    链接:https://leetcode.cn/problems/shortest-path-to-get-all-keys/solutions/1960544/by-ac_oier-5gxc/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    
    
  • 复杂度

    • 时间复杂度: O ( n ∗ m ∗ 2 k ) O(n*m*2^k) O(nm2k)
    • 空间复杂度: O ( n ∗ m ∗ 2 k ) O(n*m*2^k) O(nm2k)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值