《leetcode刷题》-864-获取所有钥匙的最短路径-困难

题目

给定一个二维网格 g r i d grid grid,其中:

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

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

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

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


实例

在这里插入图片描述

提示:

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

思路

网格中的最短路径,一般能联想到的就是 B F S BFS BFS

以普通的 B F S BFS BFS不同,该题的 B F S BFS BFS是可以走回头路的,即同一个网格可能会经过多次。

那么,如何进行 B F S BFS BFS呢?
所有首先要明确经过某个网格都带有明确目的:去拿另一钥匙
其次要明白为什么会走回头路,走回头路的原因是必须先拿 X X X钥匙,再去拿 Y Y Y钥匙。
在这里插入图片描述
如上图,先去拿银色钥匙再去拿橙色钥匙,会重复走坐标为 ( 0 , 2 ) (0,2) (0,2)这个网格。

仔细观察,经过 ( 0 , 2 ) (0,2) (0,2)的两次所携带的钥匙状态是不一样的,因为走回头路的前提是已经拿到了钥匙,然后准备去拿下一个钥匙。

所以网格是可以重复经过的,但是每次经过时身上所携带的钥匙情况是不一样的。所有 B F S BFS BFS可以这样遍历:

  • 对于当前位置,可以走上下左右四个方向。
  • 走动的前提是,该网格在当前携带钥匙的状态下之前没有经过这个格子。【使用 v i s [ m ] [ n ] [ 2 K ] vis[m][n][2^K] vis[m][n][2K]来表示遍历状态, 2 K 2^K 2K表示 K K K把钥匙,最多 2 K 2^K 2K种状态】
  • 对于每个网格,可以使用三元组 < x , y , s t a t e > <x,y,state> <x,y,state>来表示; x , y x,y x,y表示网格位置, s t a t e state state表示状态。

对于携带钥匙的状态,可以使用状态压缩;最多6把钥匙,所以6个二进制数即可表示所有状态。

如:
s t a t e = 001000 state=001000 state=001000
表示第三把钥匙已经拿到了,其他的没有拿到。

KaTeX parse error: Expected 'EOF', got '&' at position 12: ((state>>k)&̲1)==1,表示第 k k k把钥匙拿到手。

s t a t e = = 111111 state==111111 state==111111,表示钥匙全部到手。即 s t a t e = = ( ( 1 < < k e y n u m ) − 1 ) state==((1<<key_num)-1) state==((1<<keynum)1)时。

代码

class Solution {
	public int shortestPathAllKeys(String[] grid) {
		int dir[] = {1,0,-1,0,1};//方向数组
        int m = grid.length,n=grid[0].length();//网格长宽
        int key_num = 0;//钥匙数量
        Queue <int[]>q = new LinkedList<int[]>();//用队列存储下一步遍历到的网格
        for(int i = 0;i<m;i++){//循环找到起点坐标,并统计钥匙数量
            for(int j=0;j<n;j++){
            	char c = grid[i].charAt(j);
                if(c=='@'){
                    q.add(new int[]{i,j,0});
                }else if(c>='a'&&c<='f') {
                	key_num++;
                }
            }
        }
        boolean[][][] vis = new boolean[m][n][1<<key_num];//遍历状态数组
        
        int ans = 0;
        while(!q.isEmpty()) {//当队列中还存在需要BFS的网格,一次while循环就是对队列中的网格进行一次四个方向的遍历
        	int cycle = q.size();
        	for(int t=0;t<cycle;t++) {//对队列中的网格依次进行四个方向的遍历
        		int point[] = q.poll();
        		if(point[2]==(1<<key_num)-1)return ans;//钥匙全部拿到,返回步数
            	for(int i=0;i<4;i++) {//上下左右四个方向进行遍历
            		int x = point[0]+dir[i];
            		int y = point[1]+dir[i+1];
            		int state = point[2];
            		if(x>=0&&x<m&&y>=0&&y<n) {
            			char c = grid[x].charAt(y);
            			if(c=='#') {//是一堵墙,跳过
            				continue;
            			}else if((c=='.'||c=='@')&&!vis[x][y][state]) {//是起点和空房间,当前状态没遍历过,移动到该网格,即入队列
            				q.add(new int[]{x,y,state});
            				vis[x][y][state] = true;
            			}else if(c<='f'&&c>='a') {//是钥匙,判断是否已经拿过。没拿过则拿钥匙并改变状态
            				int now_state = state|(1<<c-'a');
            				if(!vis[x][y][now_state]) {
            					q.add(new int[]{x,y,now_state});
                				vis[x][y][state] = true;
            				}
            			}else {//是锁头。判断是否当前状态是否来过和是否拿到该锁头的钥匙
            				if(!vis[x][y][state]&&((state>>(c-'A'))&1)==1) {
            						q.add(new int[]{x,y,state});
            						vis[x][y][state] = true;
            						
            				}
            			}
            		}
            	}
        	}
        	ans++;
        }
        return -1;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值