首页 > 其他分享 >Leetcode 864. 获取所有钥匙的最短路径

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

时间:2024-10-10 14:11:09浏览次数:9  
标签:状态 遍历 864 访问 grid que 钥匙 Leetcode

1.题目基本信息

1.1.题目描述

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

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

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

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

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

1.2.题目地址

https://leetcode.cn/problems/shortest-path-to-get-all-keys/description/

2.解题方法

2.1.解题思路

BFS+字符串标记钥匙状态/二进制标记钥匙状态

2.2.解题步骤

第一步,将各个钥匙字符离散化为0,1,2…的数字

第二步,使用广度优先搜索遍历每个节点。BFS的使用队列存储遍历项,遍历的项为结构为(x坐标,y坐标,当前步数,当前已获取的钥匙的状态字符串),同时使用visited集合存储已经访问的不可重复状态,访问项结构为(x坐标,y坐标,当前已获取的钥匙的状态字符串);现将钥匙的状态字符串记为keys,keys[i]=”1″代表已经获取到了离散值为i的钥匙,等于”0″则代表没有获取到。这里BFS的判断是否将下一个点(nx,ny)加入到队列的逻辑相对于普通的BFS更复杂,因为当碰到钥匙时,其前面的路径上的部分点是可以重复访问的,所以的前面的visited最后加上了keys项。下面是当面对合法位置四周的位置是否加入队列的判断逻辑,记当前周围的一个待判断点为P(nx,ny):

  • 第一,需要保证P点在网格之内,同时该点不是墙,即不为”#”;
  • 第二,当P位置为.或者#时,如果有和当前一样的钥匙状态遍历过,则跳过,反之加入队列进行访问,并将访问状态加入visited;
  • 第三,当P点的值为大写字母,则代表是一把锁,如果当前的钥匙状态中有这把锁对应的钥匙,则可以将P点的信息加入到队列que中进行访问,并使用visited记录访问状态,反之跳过;
  • 第四,当P点的值为小写字母时,代表P点为钥匙,如果添加该钥匙后,已经有的钥匙数等于所有的钥匙数,则可以直接返回路径长度,程序结束,反之,如果P点的构建的状态没有访问过,则构建P点的遍历项加入队列que进行访问,并将访问记录添加到visied中。

最后,如果遍历所有的项都没有返回一条合法的路径长度,说明不存在这么一条路径,返回-1。

3.解题代码

Python代码(字符串标记钥匙状态)

from collections import deque

class Solution:
    # BFS+字符串标记钥匙状态
    def shortestPathAllKeys(self, grid: List[str]) -> int:
        directions=[[-1,0],[0,-1],[1,0],[0,1]]
        rows,cols=len(grid),len(grid[0])
        # 第一步,将各个钥匙字符离散化为0,1,2...的数字
        sx,sy=0,0
        key2IndexMap={}
        for i in range(rows):
            for j in range(cols):
                if grid[i][j]=="@":
                    sx,sy=i,j
                elif grid[i][j].islower():
                    key2IndexMap[grid[i][j]]=len(key2IndexMap)
        # print(key2IndexMap)
        # 第二步,使用广度优先搜索遍历每个节点。BFS的使用队列存储遍历项,遍历的项为结构为(x坐标,y坐标,当前步数,当前已获取的钥匙的状态字符串),同时使用visited集合存储已经访问的不可重复状态,访问项结构为(x坐标,y坐标,当前已获取的钥匙的状态字符串);现将钥匙的状态字符串记为keys,keys[i]="1"代表已经获取到了离散值为i的钥匙,等于"0"则代表没有获取到。这里BFS的判断是否将下一个点(nx,ny)加入到队列的逻辑相对于普通的BFS更复杂,因为当碰到钥匙时,其前面的路径上的部分点是可以重复访问的,所以的前面的visited最后加上了keys项。下面是当面对合法位置四周的位置是否加入队列的判断逻辑,记当前周围的一个待判断点为P(nx,ny):第一,需要保证P点在网格之内,同时该点不是墙,即不为"#";第二,当P位置为.或者#时,如果有和当前一样的钥匙状态遍历过,则跳过,反之加入队列进行访问,并将访问状态加入visited;第三,当P点的值为大写字母,则代表是一把锁,如果当前的钥匙状态中有这把锁对应的钥匙,则可以将P点的信息加入到队列que中进行访问,并使用visited记录访问状态,反之跳过;第四,当P点的值为小写字母时,代表P点为钥匙,如果添加该钥匙后,已经有的钥匙数等于所有的钥匙数,则可以直接返回路径长度,程序结束,反之,如果P点的构建的状态没有访问过,则构建P点的遍历项加入队列que进行访问,并将访问记录添加到visied中。最后,如果遍历所有的项都没有返回一条合法的路径长度,说明不存在这么一条路径,返回-1。
        defaultKeys="0"*len(key2IndexMap)   # 字符串存储状态
        que=deque([(sx,sy,0,defaultKeys)])
        visited=set()
        while que:
            for i in range(len(que)):
                x,y,steps,oriKeys=que.popleft()
                for dx,dy in directions:
                    keys=oriKeys
                    nx,ny=x+dx,y+dy
                    if 0<=nx<rows and 0<=ny<cols and grid[nx][ny]!="#":
                        if grid[nx][ny] == "." or grid[nx][ny] == "@":
                            # 没有参观的.和@能访问
                            # print((nx,ny,steps+1,keys))
                            if (nx,ny,keys) not in visited:
                                que.append((nx,ny,steps+1,keys))
                                visited.add((nx,ny,keys))
                        elif grid[nx][ny].islower():
                            # 如果没有这个钥匙,则访问
                            keyIndex=key2IndexMap[grid[nx][ny]]
                            # print((nx,ny,steps+1,keys))
                            keys=keys[:keyIndex]+"1"+keys[keyIndex+1:]
                            if (nx,ny,keys) not in visited:
                                if keys=="1"*len(key2IndexMap):
                                    return steps+1
                                que.append((nx,ny,steps+1,keys))
                                visited.add((nx,ny,keys))
                        else:   # 大写字母,表示是把锁
                            # 如果这把锁没访问过且有对应的钥匙,则可以访问
                            # print((nx,ny,steps+1,keys))
                            matchKeyIndex=key2IndexMap[grid[nx][ny].lower()]
                            if (nx,ny,keys) not in visited and keys[matchKeyIndex]=="1":
                                que.append((nx,ny,steps+1,keys))
                                visited.add((nx,ny,keys))
            # print()
        return -1

Python代码(二进制标记钥匙状态)

from collections import deque

class Solution:
    # BFS+二进制标记钥匙状态
    def shortestPathAllKeys(self, grid: List[str]) -> int:
        directions=[[-1,0],[0,-1],[1,0],[0,1]]
        rows,cols=len(grid),len(grid[0])
        sx,sy=0,0
        key2IndexMap={}
        for i in range(rows):
            for j in range(cols):
                if grid[i][j]=="@":
                    sx,sy=i,j
                elif grid[i][j].islower():
                    key2IndexMap[grid[i][j]]=len(key2IndexMap)
        # print(key2IndexMap)
        defaultKeys=0   # 二进制存储状态
        que=deque([(sx,sy,0,defaultKeys)])
        visited=set()
        while que:
            for i in range(len(que)):
                x,y,steps,oriKeys=que.popleft()
                for dx,dy in directions:
                    keys=oriKeys
                    nx,ny=x+dx,y+dy
                    if 0<=nx<rows and 0<=ny<cols and grid[nx][ny]!="#":
                        if grid[nx][ny] == "." or grid[nx][ny] == "@":
                            # 没有参观的.和@能访问
                            # print((nx,ny,steps+1,keys))
                            if (nx,ny,keys) not in visited:
                                que.append((nx,ny,steps+1,keys))
                                visited.add((nx,ny,keys))
                        elif grid[nx][ny].islower():
                            # 如果没有这个钥匙,则访问
                            keyIndex=key2IndexMap[grid[nx][ny]]
                            # print((nx,ny,steps+1,keys))
                            keys=keys|(1<<keyIndex)
                            if (nx,ny,keys) not in visited:
                                if keys==(1<<len(key2IndexMap))-1:
                                    return steps+1
                                que.append((nx,ny,steps+1,keys))
                                visited.add((nx,ny,keys))
                        else:   # 大写字母,表示是把锁
                            # 如果这把锁没访问过且有对应的钥匙,则可以访问
                            # print((nx,ny,steps+1,keys))
                            matchKeyIndex=key2IndexMap[grid[nx][ny].lower()]
                            if (nx,ny,keys) not in visited and keys&(1<<matchKeyIndex):
                                que.append((nx,ny,steps+1,keys))
                                visited.add((nx,ny,keys))
            # print()
        return -1

4.执行结果

在这里插入图片描述

标签:状态,遍历,864,访问,grid,que,钥匙,Leetcode
From: https://www.cnblogs.com/geek0070/p/18456251

相关文章

  • (LeetCode 热题 100) 1143. 最长公共子序列(动态规划dp)
    题目:1143.最长公共子序列思路:经典动态规划dp题型,时间复杂度为0(n^2)。C++版本:classSolution{public:intlongestCommonSubsequence(stringtext1,stringtext2){intn=text1.size(),m=text2.size();//状态f[i][j]表示:text1[0,i]和text2[0......
  • Leetcode 18. 四数之和
    1.题目基本信息1.1.题目描述给你一个由n个整数组成的数组nums,和一个目标值target。请你找出并返回满足下述全部条件且不重复的四元组[nums[a],nums[b],nums[c],nums[d]](若两个四元组元素一一对应,则认为两个四元组重复):0<=a,b,c,d<na、b、c和d互不相同nu......
  • LeetCode 1371. Find the Longest Substring Containing Vowels in Even Counts
    原题链接在这里:https://leetcode.com/problems/find-the-longest-substring-containing-vowels-in-even-counts/description/题目:Giventhestring s,returnthesizeofthelongestsubstringcontainingeachvowelanevennumberoftimes.Thatis,'a','e&......
  • LeetCode 11 Container with Most Water 解题思路和python代码
    题目:Youaregivenanintegerarrayheightoflengthn.Therearenverticallinesdrawnsuchthatthetwoendpointsoftheithlineare(i,0)and(i,height[i]).Findtwolinesthattogetherwiththex-axisformacontainer,suchthatthecontainerco......
  • LeetCode 209 Minimum Size Subarray Sum 题目解析和python代码
    题目:Givenanarrayofpositiveintegersnumsandapositiveintegertarget,returntheminimallengthofasubarraywhosesumisgreaterthanorequaltotarget.Ifthereisnosuchsubarray,return0instead.Example1:Input:target=7,nums=[2,3,......
  • leetcode 刷题day35动态规划Part04 背包问题(1049. 最后一块石头的重量 II 、494. 目标
    1049.最后一块石头的重量II思路:解题的难度在于如何转化为背包问题。本题的核心思路是让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题,和416.分割等和子集非常像了。首先,背包的容量target为sum/2向下取整,得到dp[target]就是分出来较小的一堆,用su......
  • leetcode 刷题day37动态规划Part06背包问题( 322. 零钱兑换、279.完全平方数、139.单词
    322.零钱兑换思路:每种硬币的数量是无限的,是典型的完全背包问题。但是题目要求等于目标值的最小硬币个数。所以这里需要对动规五部曲进行分析。动规五部曲:1、确定dp数组以及下标的含义dp[j]:凑足总额为j所需钱币的最少个数为dp[j]2、确定递推公式凑足总额为j-coins[i......
  • leetcode313. 超级丑数
    超级丑数 是一个正整数,并满足其所有质因数都出现在质数数组 primes 中。给你一个整数 n 和一个整数数组 primes ,返回第 n 个 超级丑数 。题目数据保证第 n 个 超级丑数 在 32-bit 带符号整数范围内。示例1:输入:n=12,primes=[2,7,13,19]输出:32解......
  • leetcode926. 将字符串翻转到单调递增
    如果一个二进制字符串,是以一些 0(可能没有 0)后面跟着一些 1(也可能没有 1)的形式组成的,那么该字符串是 单调递增 的。给你一个二进制字符串 s,你可以将任何 0 翻转为 1 或者将 1 翻转为 0 。返回使 s 单调递增的最小翻转次数。示例1:输入:s="00110"输出:1......
  • Leetcode 37. 解数独
    1.题目基本信息1.1.题目描述编写一个程序,通过填充空格来解决数独问题。数独的解法需遵循如下规则:数字1-9在每一行只能出现一次。数字1-9在每一列只能出现一次。数字1-9在每一个以粗实线分隔的3×3宫内只能出现一次。(请参考示例图)数独部分空格内已填入了数字,空白......