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

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

给定一个二维网格 grid。"."代表一个空房间, "#"代表一堵墙, "@"是起点,("a", "b", ...)代表钥匙,("A", "B", ...)代表锁。

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

假设 K 为钥匙(锁)的个数,且满足 1 <= K <= 6,字母表中的前 K 个字母在网格中都有自己对应的一个小写和一个大写字母。换言之,每个锁有唯一对应的钥匙,每个钥匙也有唯一对应的锁。另外,代表钥匙和锁的字母互为大小写并按字母顺序排列。
返回获取所有钥匙所需要的移动的最少次数。如果无法获取所有钥匙,返回 -1 。

输入:["@.a.#",
	  "###.#",
	  "b.A.B"]
输出:8   `@->.->a->.->.->.->A->.->b`

思考

题目要求找到所有的钥匙。将给出的网格当作图,用图遍历的方法进行求解。这样的思路,本身不难想到。

例如:有三把钥匙a,b,c

"@..c.",
"BD.#C",
"a.Adb",

想要拿到全部钥匙,必须先拿到bd钥匙,打开BD锁,才可以拿到a钥匙;

传统的深搜dfs广搜bfs,都会记录走过的路径,来节点去重,一个节点遍历之后,将其置为已遍历,之后不会再次遍历这个节点,这样才能保证算法正常退出,否则会陷入循环之中

考虑上面的例子。通过c和C,拿到bd后。在二维图上,这一路径都被标志为已遍历。算法中,无法按原路返回了。即无法从bd再退到c。
但是要拿到a,必须 从bd再退到c。
这就产生矛盾了。要保证算法正确退出,必须要对已经过的节点,设置为已遍历。又要求从bd按原路返回到c。

解决办法

从c到bd,是为了拿bd。在拿到bd前,是没有bd的。

当拿到bd后,返回c时,就多了bd的信息。

因此,在二维图的基础上,加上已拥有的钥匙信息。将图从二维扩充到三维图,这就解决了上面的矛盾。

尽管从bd回到c,在二维图上是按原路返回,但在三维图上,却不是遍历已经遍历过的节点。

三维图节点:
(x,y,k)——二维坐标和已拿到的钥匙信息。这是将钥匙对应到整数k的低6个bit。6个bit中,每个bit对应一个钥匙。
d——起始节点到(x,y,k)的最短距离。即广搜走的步数


class Solution {
    struct node{
        int x;
        int y;
        int s;
        int d;
        node(int _x,int _y,int _s,int _d):x(_x),y(_y),s(_s),d(_d){
        }
    };
    int dirs[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
public:
    bool valid(int i,int j,int n,int m){
        if(i >= 0 && i < n && j >= 0 && j < m ) return true;
        return false;
    }

    int shortestPathAllKeys(vector<string>& grid) {
        int n =grid.size(),m=grid[0].size();
        int x,y,key=0,res=0;
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                if(grid[i][j]=='@'){
                    x = i; 
                    y = j;
                }else if(grid[i][j]>='a' && grid[i][j]<='z'){
                    key |= (1<<(grid[i][j]-'a'));
                }
            }
        }

        queue<node> mq;
        vector<vector<vector<bool>>> dp(n,vector<vector<bool>>(m,vector<bool>(64,false)));
        
        mq.push(node(x,y,0,0));
        dp[x][y][0] = true;

        while(!mq.empty()){
            node cur = mq.front();
            mq.pop();
            if(cur.s == key) return cur.d;

            for(int d=0;d<4;d++){
                int nx=cur.x+dirs[d][0];
                int ny=cur.y+dirs[d][1];
                int cur_s = cur.s;

            if(valid(nx,ny,n,m) && !dp[nx][ny][cur.s] && grid[nx][ny]!='#'){
                if(grid[nx][ny]>='A' && grid[nx][ny]<='Z' && !(cur.s & (1<<(grid[nx][ny]-'A'))))  continue;
                
                if(grid[nx][ny]>='a' && grid[nx][ny]<='z'){
                    cur_s |= (1<<(grid[nx][ny]-'a')); 
                }
                    dp[nx][ny][cur_s] = true;
                    mq.push(node(nx,ny,cur_s,cur.d+1));
                }
            }  
        }
    return -1;
    }

};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值