864. 获取所有钥匙的最短路径

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

  • '.' 代表一个空房间
  • '#' 代表一堵
  • '@'
  • 小写字母代表钥匙
  • 大写字母代表锁

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

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

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

示例 1:

[leetcode每日一题]11.10_BFS

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

示例 2:

[leetcode每日一题]11.10_BFS_02

输入:grid = ["@..aA","..B#.","....b"]
输出:6

示例 3:

[leetcode每日一题]11.10_BFS_03

输入: grid = ["@Aa"]
输出: -1

提示:

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

Solution

难以绷住,因为以前从没写过BFS的题,今天一上来就是一个Hard,且BFS存储的状态也很复杂。但是因为想全勤打卡一个月,所以还是硬着头皮写(实际上是不想看论文)。

当然全靠自己写是不可能的,从题解那里得到了一些精髓。

记录一下思考的过程。首先遍历一遍地图获取起点地址和钥匙数目。不考虑剪枝写了一个版本,使用队列,每次从队列开始的状态出发,上下左右移动到合法的下一步,当移动到拿到所有钥匙的时候停止。

这里需要使用一个数据结构存储坐标、移动步数和目前获得的钥匙数。很有意思,昨天的题目用比特来表示字符串,今天也可以用比特来表示钥匙。

在遍历的时候有三种情况。第一种是遍历到空地或者'@'(也就是起点),此时直接加入到队列就行。第二种是遍历到钥匙的时候,此时直接将当前的钥匙串一下钥匙即可更新钥匙串。第三种是遍历到锁的时候,此时将钥匙的对应位置一下锁,判断能不能通过。

代码实际上可以优化,这里对四个方向的遍历可以用循环实现,但是就先这样吧。。。不想改了。

class Solution {
private:
struct pos{
int a;
int b;
int d;
int key;
};
public:
int shortestPathAllKeys(vector<string>& grid) {
int n = grid.size();
int m = grid[0].size();
int key_n = 0;
int init_pr = -1, init_pc = -1;
for(int i=0;i<grid.size();i++){
for(int j=0;j<grid[0].size();j++){
if(isalpha(grid[i][j]))key_n++;
else if(grid[i][j]=='@'){
init_pr = i;
init_pc = j;
}
}
}
key_n /= 2;
// vector<vector<vector<int>>> min_d(n, vector<int>(m, vector<int>(31, -1)));
const int suc = (1<<key_n) - 1;
queue<pos> q;
struct pos st{init_pr, init_pc, 0, 0};
// min_d[init_pr][init_pc][0] = 0;
q.emplace(st);
while(!q.empty()){
auto f = q.front();
q.pop();
if(f.key==suc)return f.d;
if(f.d>n*m)break;
if(f.a-1>-1&&grid[f.a-1][f.b]!='#'){
struct pos f1 = f;
if(grid[f.a-1][f.b]=='.'||grid[f.a-1][f.b]=='@'){
f1.a -= 1;
f1.d += 1;
q.emplace(f1);
}
else if(grid[f.a-1][f.b]<='f'&&grid[f.a-1][f.b]>='a'){
f1.a -= 1;
f1.d += 1;
f1.key |= 1<<(grid[f.a-1][f.b]-'a');
q.emplace(f1);
}
else if(grid[f.a-1][f.b]<='F'&&grid[f.a-1][f.b]>='A'&&(f.key>>(grid[f.a-1][f.b]-'A'))&1){
f1.a -= 1;
f1.d += 1;
q.emplace(f1);
}
}
if(f.a+1<n&&grid[f.a+1][f.b]!='#'){
struct pos f1 = f;
if(grid[f.a+1][f.b]=='.'||grid[f.a+1][f.b]=='@'){
f1.a += 1;
f1.d += 1;
q.emplace(f1);
}
else if(grid[f.a+1][f.b]<='f'&&grid[f.a+1][f.b]>='a'){
f1.a += 1;
f1.d += 1;
f1.key |= 1<<(grid[f.a+1][f.b]-'a');
q.emplace(f1);
}
else if(grid[f.a+1][f.b]<='F'&&grid[f.a+1][f.b]>='A'&&(f.key>>(grid[f.a+1][f.b]-'A'))&1){
f1.a += 1;
f1.d += 1;
q.emplace(f1);
}
}
if(f.b-1>-1&&grid[f.a][f.b-1]!='#'){
struct pos f1 = f;
if(grid[f.a][f.b-1]=='.'||grid[f.a][f.b-1]=='@'){
f1.b -= 1;
f1.d += 1;
q.emplace(f1);
}
else if(grid[f.a][f.b-1]<='f'&&grid[f.a][f.b-1]>='a'){
f1.b -= 1;
f1.d += 1;
f1.key |= 1<<(grid[f.a][f.b-1]-'a');
q.emplace(f1);
}
else if(grid[f.a][f.b-1]<='F'&&grid[f.a][f.b-1]>='A'&&(f.key>>(grid[f.a][f.b-1]-'A'))&1){
f1.b -= 1;
f1.d += 1;
q.emplace(f1);
}
}
if(f.b+1<m&&grid[f.a][f.b+1]!='#'){
struct pos f1 = f;
if(grid[f.a][f.b+1]=='.'||grid[f.a][f.b+1]=='@'){
f1.b += 1;
f1.d += 1;
q.emplace(f1);
}
else if(grid[f.a][f.b+1]<='f'&&grid[f.a][f.b+1]>='a'){
f1.b += 1;
f1.d += 1;
f1.key |= 1<<(grid[f.a][f.b+1]-'a');
q.emplace(f1);
}
else if(grid[f.a][f.b+1]<='F'&&grid[f.a][f.b+1]>='A'&&(f.key>>(grid[f.a][f.b+1]-'A'))&1){
f1.b += 1;
f1.d += 1;
q.emplace(f1);
}
}
}
return -1;

}
};

这样子肯定是不能过的,所以需要考虑剪枝。用一个数组min_d[row][col][key]来存储状态。初始赋值为-1,之后,当遍历到一个状态时,就更新一下这个状态(根据队列FIFO的特性,如果该状态已被赋值,那么此时一定是最小的距离,之后遍历到这个状态的时候距离一定不会更小)。用哈希表存也可以。

当队列为空,表示无法达到。返回-1.

代码(C++)

class Solution {
private:
struct pos{
int a;
int b;
int d;
int key;
};
public:
int shortestPathAllKeys(vector<string>& grid) {
int n = grid.size();
int m = grid[0].size();
int key_n = 0;
int init_pr = -1, init_pc = -1;
for(int i=0;i<grid.size();i++){
for(int j=0;j<grid[0].size();j++){
if(isalpha(grid[i][j]))key_n++;
else if(grid[i][j]=='@'){
init_pr = i;
init_pc = j;
}
}
}
key_n /= 2;// 在上面的循环中,找到了起点位置,且获取了钥匙和锁的总数,除以2就是要收集的\U0001f511数目
vector<vector<vector<int>>> min_d(n, vector<vector<int>>(m, vector<int>(64, -1)));
const int suc = (1<<key_n) - 1;
queue<pos> q;
struct pos st{init_pr, init_pc, 0, 0};
min_d[init_pr][init_pc][0] = 0;
q.emplace(st);
while(!q.empty()){
auto f = q.front();
q.pop();
if(f.key==suc)return f.d;
if(f.a-1>-1&&grid[f.a-1][f.b]!='#'){
struct pos f1 = f;
if(grid[f.a-1][f.b]=='.'||grid[f.a-1][f.b]=='@'){
f1.a -= 1;
f1.d += 1;
if(min_d[f1.a][f1.b][f1.key]==-1){
q.emplace(f1);
min_d[f1.a][f1.b][f1.key] = f1.d;
}
}
else if(grid[f.a-1][f.b]<='f'&&grid[f.a-1][f.b]>='a'){
f1.a -= 1;
f1.d += 1;
f1.key |= 1<<(grid[f.a-1][f.b]-'a');
if(min_d[f1.a][f1.b][f1.key]==-1){
q.emplace(f1);
min_d[f1.a][f1.b][f1.key] = f1.d;
}
}
else if(grid[f.a-1][f.b]<='F'&&grid[f.a-1][f.b]>='A'&&(f.key>>(grid[f.a-1][f.b]-'A'))&1){
f1.a -= 1;
f1.d += 1;
if(min_d[f1.a][f1.b][f1.key]==-1){
q.emplace(f1);
min_d[f1.a][f1.b][f1.key] = f1.d;
}
}
}
if(f.a+1<n&&grid[f.a+1][f.b]!='#'){
struct pos f1 = f;
if(grid[f.a+1][f.b]=='.'||grid[f.a+1][f.b]=='@'){
f1.a += 1;
f1.d += 1;
if(min_d[f1.a][f1.b][f1.key]==-1){
q.emplace(f1);
min_d[f1.a][f1.b][f1.key] = f1.d;
}
}
else if(grid[f.a+1][f.b]<='f'&&grid[f.a+1][f.b]>='a'){
f1.a += 1;
f1.d += 1;
f1.key |= 1<<(grid[f.a+1][f.b]-'a');
if(min_d[f1.a][f1.b][f1.key]==-1){
q.emplace(f1);
min_d[f1.a][f1.b][f1.key] = f1.d;
}
}
else if(grid[f.a+1][f.b]<='F'&&grid[f.a+1][f.b]>='A'&&(f.key>>(grid[f.a+1][f.b]-'A'))&1){
f1.a += 1;
f1.d += 1;
if(min_d[f1.a][f1.b][f1.key]==-1){
q.emplace(f1);
min_d[f1.a][f1.b][f1.key] = f1.d;
}
}
}
if(f.b-1>-1&&grid[f.a][f.b-1]!='#'){
struct pos f1 = f;
if(grid[f.a][f.b-1]=='.'||grid[f.a][f.b-1]=='@'){
f1.b -= 1;
f1.d += 1;
if(min_d[f1.a][f1.b][f1.key]==-1){
q.emplace(f1);
min_d[f1.a][f1.b][f1.key] = f1.d;
}
}
else if(grid[f.a][f.b-1]<='f'&&grid[f.a][f.b-1]>='a'){
f1.b -= 1;
f1.d += 1;
f1.key |= 1<<(grid[f.a][f.b-1]-'a');
if(min_d[f1.a][f1.b][f1.key]==-1){
q.emplace(f1);
min_d[f1.a][f1.b][f1.key] = f1.d;
}
}
else if(grid[f.a][f.b-1]<='F'&&grid[f.a][f.b-1]>='A'&&(f.key>>(grid[f.a][f.b-1]-'A'))&1){
f1.b -= 1;
f1.d += 1;
if(min_d[f1.a][f1.b][f1.key]==-1){
q.emplace(f1);
min_d[f1.a][f1.b][f1.key] = f1.d;
}
}
}
if(f.b+1<m&&grid[f.a][f.b+1]!='#'){
struct pos f1 = f;
if(grid[f.a][f.b+1]=='.'||grid[f.a][f.b+1]=='@'){
f1.b += 1;
f1.d += 1;
if(min_d[f1.a][f1.b][f1.key]==-1){
q.emplace(f1);
min_d[f1.a][f1.b][f1.key] = f1.d;
}
}
else if(grid[f.a][f.b+1]<='f'&&grid[f.a][f.b+1]>='a'){
f1.b += 1;
f1.d += 1;
f1.key |= 1<<(grid[f.a][f.b+1]-'a');
if(min_d[f1.a][f1.b][f1.key]==-1){
q.emplace(f1);
min_d[f1.a][f1.b][f1.key] = f1.d;
}
}
else if(grid[f.a][f.b+1]<='F'&&grid[f.a][f.b+1]>='A'&&(f.key>>(grid[f.a][f.b+1]-'A'))&1){
f1.b += 1;
f1.d += 1;
if(min_d[f1.a][f1.b][f1.key]==-1){
q.emplace(f1);
min_d[f1.a][f1.b][f1.key] = f1.d;
}
}
}
}
return -1;

}
};

如果有人看到这,可以考虑帮我用一个循环把四个状态包起来。我自己懒得写了。