LeetCode--[广度优先搜索]864. 获取所有钥匙的最短路径

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

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

假设 K 为钥匙/锁的个数,且满足 1 <= K <= 6,字母表中的前 K 个字母在网格中都有自己对应的一个小写和一个大写字母。换言之,每个锁有唯一对应的钥匙,每个钥匙也有唯一对应的锁。另外,代表钥匙和锁的字母互为大小写并按字母顺序排列。
返回获取所有钥匙所需要的移动的最少次数。如果无法获取所有钥匙,返回 -1 。
示例 1:
输入:[“@.a.#”,”###.#”,”b.A.B”]
输出:8
示例 2:
输入:[“@..aA”,”..B#.”,”….b”]
输出:6

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

解答

要求最短路径,采用广度优先搜索。用队列实现广度优先搜索。
因为增加了钥匙,所以我们需要引入状态,以表达当前有多少钥匙。在同一个位置,若拥有的钥匙数目不同,则
视为不同的状态,这些状态需要入队;若当前状态此前已经访问过(即钥匙数与坐标位置都相同视为同一个状态),则
可跳过,无需访问。
key只有6种,我们用一个int类型保存当前已经拥有的key的情况,只需要6bit。
我们从起点开始搜索,遇到钥匙就更新记录,遇到墙就跳过,遇到門若有钥匙则通过,若没有钥匙则跳过。此外,如果当前
状态已经遍历过,就跳过,否则加入队列,这些属于广度优先搜索的基本操作。

# -*- coding: utf-8 -*-

'''获取所有钥匙的最短路径'''
from collections import namedtuple,deque

# 定义网格节点
GridNode = namedtuple('GridNode',['i','j','key_state'])

class Solution(object):
    def shortestPathAllKeys(self, grid):
        """
        :type grid: List[str]
        :rtype: int
        """
        # 要求最短路径,采用广度优先搜索。用队列实现广度优先搜索。
        # 因为增加了钥匙,所以我们需要引入状态,以表达当前有多少钥匙。在同一个位置,若拥有的钥匙数目不同,则
        # 视为不同的状态,这些状态需要入队;若当前状态此前已经访问过(即钥匙数与坐标位置都相同视为同一个状态),则
        # 可跳过,无需访问。
        # key只有6种,我们用一个int类型保存当前已经拥有的key的情况,只需要6bit。
        # 我们从起点开始搜索,遇到钥匙就更新记录,遇到墙就跳过,遇到門若有钥匙则通过,若没有钥匙则跳过。此外,如果当前
        # 状态已经遍历过,就跳过,否则加入队列,这些属于广度优先搜索的基本操作。
        # 需要注意以下几点。

        keys_lst = ['a','b','c','d','e','f']
        doors_lst = ['A','B','C','D','E','F']

        m = len(grid)
        n = len(grid[0])

        # 包含所有钥匙,用6bit表示
        allkey_state = 0
        # 已经访问过的状态:m*n*64
        status_seen = [[[0 for k in range(64)] for j in range(n)] for i in range(m)]

        # 读取起点和钥匙所在的位置
        for i in range(m):
            for j in range(n):
                c = grid[i][j]
                if c == '@':
                    start_i = i
                    start_j = j
                if c in keys_lst:
                    allkey_state = allkey_state | (1<< keys_lst.index(c))
        # 队列q,初始节点
        q = deque()
        q.append(GridNode(start_i, start_j, 0))
        # 搜索方向,左右上下
        dirs = [(0,-1),(0,1),(-1,0),(1,0)]
        step = 0
        # 我们要找的是找到所有key的最短路径,因此每次我们处理距离最开始的起点距离为step的节点,同时把他们的邻居入队(为下一层的节点,step+1)
        while q:
            # 先把距离最开始的起点step步长的情况下的,待处理的节点都处理完
            nq = len(q)
            while nq:
                nq = nq - 1
                # 队列头部
                node = q.popleft()
                row = node.i 
                col = node.j
                key_state = node.key_state
                # 已找到所有的钥匙
                if key_state == allkey_state:
                    return step
                # 访问节点的所有邻居
                for delta in dirs:
                    row = node.i + delta[0]
                    col = node.j + delta[1]
                    key_state = node.key_state
                    if row < 0 or row >= m or col <0 or col >=n:
                        continue
                    c = grid[row][col]

                    # 遇到墙壁
                    if c == '#':
                        continue
                    # 遇到门且没有钥匙
                    if c in doors_lst:
                        if not (1<<doors_lst.index(c)) & key_state:
                            continue
                    # 遇到钥匙则更新我们的钥匙
                    if c in keys_lst:
                        key_state = key_state | (1<<keys_lst.index(c))
                    # 判断当前的状态是否已经访问过,若否,则加入队列,等下次访问
                    if status_seen[row][col][key_state]:
                        continue
                    q.append(GridNode(row, col, key_state))
                    # 表明已经访问过
                    status_seen[row][col][key_state] = 1
            # 处理下一层的节点
            step = step + 1    
        return -1

猜你喜欢

转载自blog.csdn.net/u014230646/article/details/81189340