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

简介: n件物品,每个物品有2个状态(状态1和状态2),那么可以用n位2进制表示物品的状态

状态压缩


  • 状态压缩:一个维度能表示所有物品的状态情况


  • 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 )

目录
相关文章
|
4月前
【每日一题Day329】LC213打家劫舍Ⅱ | 动态规划
【每日一题Day329】LC213打家劫舍Ⅱ | 动态规划
33 0
|
4月前
【每日一题Day330】LC337打家劫舍Ⅲ | 动态规划
【每日一题Day330】LC337打家劫舍Ⅲ | 动态规划
31 0
|
4月前
【每日一题Day218】LC1091 二进制矩阵中的最短路径 | BFS
【每日一题Day218】LC1091 二进制矩阵中的最短路径 | BFS
28 0
|
4月前
【每日一题Day265】LC979在二叉树中分配硬币 | 树形dp
【每日一题Day265】LC979在二叉树中分配硬币 | 树形dp
48 0
|
4月前
|
存储 人工智能 BI
【每日一题Day216】LC1377 T 秒后青蛙的位置 | BFS DFS
【每日一题Day216】LC1377 T 秒后青蛙的位置 | BFS DFS
46 0
|
4月前
【每日一题Day126】LC1140石子游戏Ⅱ | 博弈dp 记忆化搜索
【每日一题Day126】LC1140石子游戏Ⅱ | 博弈dp 记忆化搜索
45 0
|
机器学习/深度学习 定位技术
【每日一题Day109】LC1210穿过迷宫的最少移动次数 | BFS+dp
思路:使用BFS搜索,队列中存放三元组[蛇尾的横轴坐标x,蛇尾的纵轴坐标y,蛇的状态],当蛇为水平时,状态为0;当蛇为竖直时,状态为1
117 1
【每日一题Day109】LC1210穿过迷宫的最少移动次数 | BFS+dp
|
机器学习/深度学习 Java
【每日一题Day64】LC1799N 次操作后的最大分数和 | 状态压缩dp 状态压缩+dfs+记忆化搜索
每个数字有两种状态,删除或者未删除,因此可以使用状态压缩记录未删除的数字情况state,便于使用位运算进行状态的遍历及转移;使用状态压缩dp找到将所以数字删除的最大分数
97 0
【每日一题Day64】LC1799N 次操作后的最大分数和 | 状态压缩dp 状态压缩+dfs+记忆化搜索
|
存储
Leetcode-每日一题1210. 穿过迷宫的最少移动次数(BFS)
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_46618592/article/details/128890280?spm=1001.2014.3001.5501
105 0
Leetcode-每日一题1210. 穿过迷宫的最少移动次数(BFS)
|
机器学习/深度学习
【每日一题Day107】LC1145二叉树着色游戏 | dfs+贪心
现在,假设你是「二号」玩家,根据所给出的输入,假如存在一个 y 值可以确保你赢得这场游戏,则返回 true ;若无法获胜,就请返回 false 。
94 0
【每日一题Day107】LC1145二叉树着色游戏 | dfs+贪心