864. Shortest Path to Get All Keys - cocoder39/coco39_LC GitHub Wiki

864. Shortest Path to Get All Keys

For 2-dimension bfs we use a set to avoid reentering a grid. For this problem, we sometime need to reenter a grid. We introduce one more dimension-state of acquired keys- to dedup.

at most 6 keys -> 6! states on the 3rd dimension. So each grid can be visited at most 64 times, thus total time complexity is O(k! * m * n) where k is the number of keys

class Solution(object):
    def shortestPathAllKeys(self, grid):
        """
        :type grid: List[str]
        :rtype: int
        """
        m, n = len(grid), len(grid[0])
        numOfKeys = 0
        for i in range(m):
            for j in range(n):
                if grid[i][j] == '@':
                    starti = i
                    startj = j
                elif grid[i][j] in "abcdef":
                    numOfKeys += 1
        
        direc = [0,1],[0,-1],[1,0],[-1,0](/cocoder39/coco39_LC/wiki/0,1],[0,-1],[1,0],[-1,0)
        moves = set()
        queue = collections.deque([starti, startj, "", 0](/cocoder39/coco39_LC/wiki/starti,-startj,-"",-0))
        while queue:
            i, j, keys, stepCount = queue.popleft()

            if grid[i][j] in "abcdef" and grid[i][j] not in keys:
                keys += grid[i][j]
            
            if len(keys) == numOfKeys:
                return stepCount

            for x, y in direc:
                ni = i+x
                nj = j+y
                if 0 <= ni < m and 0 <= nj < n and (grid[ni][nj] in ".@abcdef" or grid[ni][nj].lower() in keys):
                    if (ni, nj, keys) not in moves:
                        moves.add((ni,nj,keys))
                        queue.append([ni, nj, keys, stepCount + 1])
                
        return -1

option 2: further optimization using bit to record each key state, so in total 2^k states rather than k! states in option 1. Total complexity is O(m * n * 2^k)

  • to acquire a key: keys |= 1 << (ord(grid[i][j]) - ord('a'))
  • to check if all keys have been acquired: if keys == (1 << numOfKeys) - 1:
  • to check if key has/ hasn't been acquired: (keys >> (ord(grid[ni][nj]) - ord('A'))) & 1 == 0
class Solution(object):
    def shortestPathAllKeys(self, grid):
        """
        :type grid: List[str]
        :rtype: int
        """
        m, n = len(grid), len(grid[0])
        numOfKeys = 0
        for i in range(m):
            for j in range(n):
                if grid[i][j] == '@':
                    starti = i
                    startj = j
                elif grid[i][j] in "abcdef":
                    numOfKeys += 1
        
        direc = [0,1],[0,-1],[1,0],[-1,0](/cocoder39/coco39_LC/wiki/0,1],[0,-1],[1,0],[-1,0)
        moves = set()
        queue = collections.deque([starti, startj, 0, 0](/cocoder39/coco39_LC/wiki/starti,-startj,-0,-0))
        while queue:
            i, j, keys, stepCount = queue.popleft()

            if grid[i][j] in "abcdef":
                keys |= 1 << (ord(grid[i][j]) - ord('a'))
            
            if keys == (1 << numOfKeys) - 1:
                return stepCount

            for x, y in direc:
                ni = i+x
                nj = j+y
                if 0 <= ni < m and 0 <= nj < n:
                    if grid[ni][nj] == '#':
                        continue
                    elif grid[ni][nj] in "ABCDEF" and ((keys >> (ord(grid[ni][nj]) - ord('A'))) & 1 == 0):
                        continue
                    if (ni, nj, keys) not in moves:
                        moves.add((ni,nj,keys))
                        queue.append([ni, nj, keys, stepCount + 1])
                
        return -1