LeetCode 0864. 获取所有钥匙的最短路径:广搜 + 状压
Posted Tisfy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode 0864. 获取所有钥匙的最短路径:广搜 + 状压相关的知识,希望对你有一定的参考价值。
【LetMeFly】864.获取所有钥匙的最短路径:广搜 + 状压
力扣题目链接:https://leetcode.cn/problems/shortest-path-to-get-all-keys/
给定一个二维网格 grid
,其中:
- '.' 代表一个空房间
- '#' 代表一堵
- '@' 是起点
- 小写字母代表钥匙
- 大写字母代表锁
我们从起点开始出发,一次移动是指向四个基本方向之一行走一个单位空间。我们不能在网格外面行走,也无法穿过一堵墙。如果途经一个钥匙,我们就把它捡起来。除非我们手里有对应的钥匙,否则无法通过锁。
假设 k 为 钥匙/锁 的个数,且满足 1 <= k <= 6
,字母表中的前 k
个字母在网格中都有自己对应的一个小写和一个大写字母。换言之,每个锁有唯一对应的钥匙,每个钥匙也有唯一对应的锁。另外,代表钥匙和锁的字母互为大小写并按字母顺序排列。
返回获取所有钥匙所需要的移动的最少次数。如果无法获取所有钥匙,返回 -1
。
示例 1:
输入:grid = ["@.a.#","###.#","b.A.B"] 输出:8 解释:目标是获得所有钥匙,而不是打开所有锁。
示例 2:
输入:grid = ["@..aA","..B#.","....b"] 输出:6
示例 3:
输入: grid = ["@Aa"] 输出: -1
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 30
grid[i][j]
只含有'.'
,'#'
,'@'
,'a'-
'f
'
以及'A'-'F'
- 钥匙的数目范围是
[1, 6]
- 每个钥匙都对应一个 不同 的字母
- 每个钥匙正好打开一个对应的锁
方法一:广搜 + 状压
如果不考虑钥匙和锁的问题,那么一个简单的广搜就解决了。
广搜是“走完一步能到达的位置”,然后“走完两步能到达的位置”,“…”
因此第一次搜到终点所走的步数即为答案。
本题中,增加了钥匙与锁,那么,不如把普通广搜的“位置”替换为“状态”
其中,状态包含:坐标和钥匙搜集情况
如果两次到达“同一位置”时所携带的钥匙情况不同,那么我们就认为这是“两种状态”
怎么处理钥匙收集情况呢?地图中最多有6把钥匙,因此我们可以用6位二进制数来分别代表6把钥匙。某位为1代表已获得该钥匙,0代表未获得。
这样,我们就可以把普通的广搜:
struct Node
int x, y;
int step;
;
queue<Node> q;
q.push(startX, startY, 0);
bool visited[n][m] = false;
visited[startX][startY] = true;
while (q.size())
auto [thisX, thisY, thisStep] = q.front();
q.pop();
for (int d = 0; d < 4; d++)
int tx = x + directions[d][0];
int ty = y + directions[d][1];
if (tx >= 0 && tx < n && ty >= 0 && ty < n)
if (!visited[tx][ty])
visited[tx][ty] = true;
q.push(tx, ty, thisStep + 1);
if (tx == targetX && ty == targetY)
return thisStep + 1;
return -1;
修改为:
struct Node
int x, y;
int mask; // Change
int step;
;
queue<Node> q;
q.push(startX, startY, 0, 0);
bool visited[n][m] = false;
visited[startX][startY][1 << keyNum] = true;
while (q.size())
auto [thisX, thisY, thisMask, thisStep] = q.front();
q.pop();
for (int d = 0; d < 4; d++)
int tx = x + directions[d][0];
int ty = y + directions[d][1];
if (tx >= 0 && tx < n && ty >= 0 && ty < n)
int newMask = mask; // Change
if (isKey(grid[tx][ty]))
newMask |= (1 << getTh(grid[tx][ty]));
if (!visited[tx][ty][newMask]) // Change: visited[tx][ty] -> visited[tx][ty][newMask]
visited[tx][ty][newMask] = true;
q.push(tx, ty, newMask, thisStep + 1);
if (tx == targetX && ty == targetY && newMask == AllKeyMask())
return thisStep + 1;
return -1;
说白了就是在广搜“位置”的基础上加一个“钥匙状态”
之后在广搜的过程中:
- 遇到“未到达过的空地”,就尝试到达(广搜)
- 遇到“钥匙”,就计算获得钥匙后的新状态,若“钥匙坐标&新状态”未到达过,就尝试到达(广搜)
- 遇到“锁”,就看当前的“钥匙状态”中是否包含这把锁的钥匙,若“包含钥匙&锁的位置状态未到达过”,就尝试到达(广搜)
广搜期间,一旦出现“获得一把钥匙后 集齐了所有的钥匙”,就返回当前步数作为答案。
广搜结束(未集齐全部钥匙)则返回-1
- 时间复杂度 O ( n m × k 2 ) O(nm\\times k^2) O(nm×k2),其中 g i r d . s i z e ( ) = n × m gird.size() = n\\times m gird.size()=n×m,钥匙数量未 k k k
- 空间复杂度 O ( n m × k 2 ) O(nm\\times k^2) O(nm×k2)
AC代码
C++
struct MyNode
int x, y;
int mask;
int step;
MyNode(int x, int y) : x(x), y(y), mask(0), step(0)
;
const int directions[4][2] = -1, 0, 1, 0, 0, -1, 0, 1;
class Solution
public:
int shortestPathAllKeys(vector<string>& grid)
int startX, startY;
int cntLock = 0;
int n = grid.size(), m = grid[0].size();
int key2th[26];
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
if (islower(grid[i][j]))
key2th[grid[i][j] - 'a'] = cntLock++;
else if (grid[i][j] == '@')
startX = i, startY = j;
vector<vector<vector<bool>>> visited(n, vector<vector<bool>>(m, vector<bool>(1 << cntLock, false)));
queue<MyNode> q;
q.push(MyNode(startX, startY));
visited[startX][startY][0] = true;
while (q.size())
MyNode thisNode = q.front();
q.pop();
int thisX = thisNode.x, thisY = thisNode.y, thisMask = thisNode.mask;
// if (thisX == 1 && thisY == 4)
// puts("Debug begin"); //********
//
for (int d = 0; d < 4; d++)
int tx = thisX + directions[d][0], ty = thisY + directions[d][1];
if (tx >= 0 && tx < n && ty >= 0 && ty < m && grid[tx][ty] != '#')
if (grid[tx][ty] == '.' || grid[tx][ty] == '@') // @也是空地!!!
if (!visited[tx][ty][thisMask])
visited[tx][ty][thisMask] = true;
MyNode newNode = thisNode;
newNode.x = tx, newNode.y = ty;
newNode.step++;
q.push(newNode);
else if (islower(grid[tx][ty]))
int toMask = thisMask | (1 << key2th[grid[tx][ty] - 'a']);
if (!visited[tx][ty][toMask])
visited[tx][ty][toMask] = true;
MyNode newNode = thisNode;
newNode.x = tx, newNode.y = ty;
newNode.step++;
newNode.mask = toMask;
q.push(newNode);
if (toMask == (1 << cntLock) - 1)
return newNode.step;
else if (isupper(grid[tx][ty]))
if (thisMask & (1 << key2th[grid[tx][ty] - 'A']))
if (!visited[tx][ty][thisMask])
visited[tx][ty][thisMask] = true;
MyNode newNode = thisNode;
newNode.x = tx, newNode.y = ty;
newNode.step++;
q.push(newNode);
return -1;
;
同步发文于CSDN,原创不易,转载请附上原文链接哦~
Tisfy:https://letmefly.blog.csdn.net/article/details/127784094
以上是关于LeetCode 0864. 获取所有钥匙的最短路径:广搜 + 状压的主要内容,如果未能解决你的问题,请参考以下文章
LeetCode 847 访问所有节点的最短路径[BFS] HERODING的LeetCode之路
[LeetCode] 847. Shortest Path Visiting All Nodes 访问所有结点的最短路径
LeetCode 789. 逃脱阻碍者(贪心) / 1646. 获取生成数组中的最大值 / 787. K 站中转内最便宜的航班(有限制的最短路,重新审视迪杰斯特拉,动态规划)
⭐算法入门⭐《动态规划 - 状态压缩DP》困难01 —— LeetCode 847. 访问所有节点的最短路径
LeetCode802. 找到最终的安全状态(图论三色标记法拓扑排序)/847. 访问所有节点的最短路径(特殊的bfs,状态压缩,dp)