【2022-11-10每日一题】864. 获取所有钥匙的最短路径[Hard]

2022-11-10
3分钟阅读时长

2022-11-10每日一题:864. 获取所有钥匙的最短路径

难度:Hard

标签:位运算 、 广度优先搜索 、 数组 、 矩阵

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

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

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

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

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

 

示例 1:

输入:grid = ["@.a.#","###.#","b.A.B"]
输出:8
解释:目标是获得所有钥匙,而不是打开所有锁。

示例 2:

输入:grid = ["@..aA","..B#.","....b"]
输出:6

示例 3:

输入: grid = ["@Aa"]
输出: -1

 

提示:

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

方法一:状态压缩+广度优先搜索

写法一

func shortestPathAllKeys(grid []string) int {
    // 获取钥匙个数和起点坐标
    var k, si, sj int
    for i, row := range grid {
        for j, c := range row {
            if c == '@' {
                si, sj = i, j // 起点
            } else if c >= 'a' && c <= 'z' {
                k++ // 累加钥匙个数
            }
        }
    }
    m, n := len(grid), len(grid[0])
    type tuple struct { i, j, state int }
    q := []tuple{{si, sj, 0}} // 初始化队列
    visited := map[tuple]bool{{si, sj, 0}: true} // 判重初始化
    dirs := []int{-1, 0, 1, 0, -1} // 上右下左 四个方向
    
    ans := 0 // 结果
    // 广度优先搜索
    for len(q) > 0 {
        for t := len(q); t > 0; t-- {
            p := q[0]
            q = q[1:]
            i, j, state := p.i, p.j, p.state
            // 找到所有钥匙,返回当前步数
            if p.state == 1<<k-1 {
                return ans
            }
            // 四个方向搜索
            for h := 0; h < 4; h++ {
                x, y := i+dirs[h], j+dirs[h+1]
                // 在边界范围内
                if 0 <= x && x < m && 0 <= y && y < n {
                    c := grid[x][y]
                    // 是墙,或者是锁,但此时没有对应的钥匙,无法通过
                    if c == '#' || (c >= 'A' && c <= 'Z' && (state>>(c-'A')&1 == 0)) {
                        continue
                    }
                    next := state
                    // 是钥匙, 更新状态
                    if c >= 'a' && c <= 'z' {
                        next |= 1 << (c-'a')
                    }
                    // 此状态未访问过,入队
                    if !visited[tuple{x, y, next}] {
                        q = append(q, tuple{x, y, next})
                        visited[tuple{x, y, next}] = true
                    }
                }
            }
        }
        ans++ // 步数加一
    }
	return -1
}

写法二

var dirs = []struct{ x, y int }{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}

func shortestPathAllKeys(grid []string) int {
    m, n := len(grid), len(grid[0])
    // 遍历获取开始坐标和钥匙编号
    sx, sy := 0, 0
    keyToIdx := map[rune]int{}
    for i, row := range grid {
        for j, c := range row {
            if c == '@' {
                sx, sy = i, j
            } else if unicode.IsLower(c) {
                if _, ok := keyToIdx[c]; !ok {
                    keyToIdx[c] = len(keyToIdx) // 给钥匙编号
                }
            }
        }
    }

    // 状态初始化
    dist := make([][][]int, m)
    for i := range dist {
        dist[i] = make([][]int, n)
        for j := range dist[i] {
            dist[i][j] = make([]int, 1<<len(keyToIdx))
            for k := range dist[i][j] {
                dist[i][j][k] = -1
            }
        }
    }
    dist[sx][sy][0] = 0
    q := [][3]int{{sx, sy, 0}}
    for len(q) > 0 {
        p := q[0]
        q = q[1:]
        x, y, mask := p[0], p[1], p[2]
        // 遍历四个方向
        for _, d := range dirs {
            nx, ny := x+d.x, y+d.y
            // 边界处理
            if 0 <= nx && nx < m && 0 <= ny && ny < n && grid[nx][ny] != '#' {
                c := rune(grid[nx][ny])
                if c == '.' || c == '@' { // 空格或者开始坐标
                    if dist[nx][ny][mask] == -1 {
                        dist[nx][ny][mask] = dist[x][y][mask] + 1
                        q = append(q, [3]int{nx, ny, mask})
                    }
                } else if unicode.IsLower(c) { // 钥匙
                    t := mask | 1<<keyToIdx[c]
                    if dist[nx][ny][t] == -1 {
                        dist[nx][ny][t] = dist[x][y][mask] + 1
                        if t == 1<<len(keyToIdx)-1 {
                            return dist[nx][ny][t]
                        }
                        q = append(q, [3]int{nx, ny, t})
                    }
                } else { // 锁
                    idx := keyToIdx[unicode.ToLower(c)]
                    if mask>>idx&1 > 0 && dist[nx][ny][mask] == -1 {
                        dist[nx][ny][mask] = dist[x][y][mask] + 1
                        q = append(q, [3]int{nx, ny, mask})
                    }
                }
            }
        }
    }
    return -1
}

复杂度分析

  • 时间复杂度:O(mn⋅2k)O(mn⋅2^k),其中 m 和 n 是网格的行数和列数,k 是网格中钥匙的数量。

  • 空间复杂度:O(mn⋅2k)O(mn⋅2^k)。

LeetCode题库地址