diff --git "a/Index/\345\233\276\350\256\272 BFS.md" "b/Index/\345\233\276\350\256\272 BFS.md" index e1958d78..8fde688e 100644 --- "a/Index/\345\233\276\350\256\272 BFS.md" +++ "b/Index/\345\233\276\350\256\272 BFS.md" @@ -11,6 +11,7 @@ | [815. 公交路线](https://leetcode-cn.com/problems/bus-routes/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/bus-routes/solution/gong-shui-san-xie-yi-ti-shuang-jie-po-su-1roh/) | 困难 | 🤩🤩🤩🤩 | | [847. 访问所有节点的最短路径](https://leetcode-cn.com/problems/shortest-path-visiting-all-nodes/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/shortest-path-visiting-all-nodes/solution/gong-shui-san-xie-yi-ti-shuang-jie-bfs-z-6p2k/) | 困难 | 🤩🤩🤩🤩🤩 | | [863. 二叉树中所有距离为 K 的结点](https://leetcode-cn.com/problems/all-nodes-distance-k-in-binary-tree/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/all-nodes-distance-k-in-binary-tree/solution/gong-shui-san-xie-yi-ti-shuang-jie-jian-x6hak/) | 中等 | 🤩🤩🤩🤩 | +| [864. 获取所有钥匙的最短路径](https://leetcode.cn/problems/shortest-path-to-get-all-keys/) | [LeetCode 题解链接](https://leetcode.cn/problems/shortest-path-to-get-all-keys/solution/by-ac_oier-5gxc/) | 困难 | 🤩🤩🤩🤩🤩 | | [909. 蛇梯棋](https://leetcode-cn.com/problems/snakes-and-ladders/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/snakes-and-ladders/solution/gong-shui-san-xie-bfs-mo-ni-by-ac_oier-woh6/) | 中等 | 🤩🤩🤩🤩 | | [1020. 飞地的数量](https://leetcode-cn.com/problems/number-of-enclaves/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/number-of-enclaves/solution/gong-shui-san-xie-bing-cha-ji-dfs-yun-yo-oyh1/) | 中等 | 🤩🤩🤩 | | [1034. 边界着色](https://leetcode-cn.com/problems/coloring-a-border/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/coloring-a-border/solution/gong-shui-san-xie-tu-lun-sou-suo-zhuan-t-snvw/) | 中等 | 🤩🤩🤩🤩 | diff --git "a/Index/\346\250\241\346\213\237.md" "b/Index/\346\250\241\346\213\237.md" index c9232eae..9cbebdc6 100644 --- "a/Index/\346\250\241\346\213\237.md" +++ "b/Index/\346\250\241\346\213\237.md" @@ -100,6 +100,7 @@ | [747. 至少是其他数字两倍的最大数](https://leetcode-cn.com/problems/largest-number-at-least-twice-of-others/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/largest-number-at-least-twice-of-others/solution/gong-shui-san-xie-jian-dan-mo-ni-ti-by-a-8179/) | 简单 | 🤩🤩🤩🤩🤩 | | [748. 最短补全词](https://leetcode-cn.com/problems/shortest-completing-word/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/shortest-completing-word/solution/gong-shui-san-xie-jian-dan-zi-fu-chuan-j-x4ao/) | 简单 | 🤩🤩🤩🤩 | | [762. 二进制表示中质数个计算置位](https://leetcode-cn.com/problems/prime-number-of-set-bits-in-binary-representation/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/prime-number-of-set-bits-in-binary-representation/solution/by-ac_oier-w50x/) | 简单 | 🤩🤩🤩🤩 | +| [764. 最大加号标志](https://leetcode.cn/problems/largest-plus-sign/) | [LeetCode 题解链接](https://leetcode.cn/problems/largest-plus-sign/solution/by-ac_oier-q932/) | 中等 | 🤩🤩🤩 | | [766. 托普利茨矩阵](https://leetcode-cn.com/problems/toeplitz-matrix/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/toeplitz-matrix/solution/cong-ci-pan-du-qu-cheng-ben-fen-xi-liang-f20w/) | 简单 | 🤩🤩🤩 | | [769. 最多能完成排序的块](https://leetcode.cn/problems/max-chunks-to-make-sorted/) | [LeetCode 题解链接](https://leetcode.cn/problems/max-chunks-to-make-sorted/solution/by-ac_oier-4uny/) | 中等 | 🤩🤩🤩🤩🤩 | | [788. 旋转数字](https://leetcode.cn/problems/rotated-digits/) | [LeetCode 题解链接](https://leetcode.cn/problems/rotated-digits/solution/by-ac_oier-9qpw/) | 中等 | 🤩🤩🤩🤩 | diff --git "a/LeetCode/1231-1240/1239. \344\270\262\350\201\224\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\244\247\351\225\277\345\272\246\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/1231-1240/1239. \344\270\262\350\201\224\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\244\247\351\225\277\345\272\246\357\274\210\344\270\255\347\255\211\357\274\211.md" index 62016638..412c69cf 100644 --- "a/LeetCode/1231-1240/1239. \344\270\262\350\201\224\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\244\247\351\225\277\345\272\246\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/1231-1240/1239. \344\270\262\350\201\224\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\244\247\351\225\277\345\272\246\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -43,19 +43,7 @@ Tag : 「DFS」、「二进制枚举」、「模拟退火」、「随机化」 ### 基本分析 -根据题意,可以将本题看做一类特殊的「数独问题」:在给定的 `arr` 字符数组中选择,尽可能多的覆盖一个 $1 * 26$ 的矩阵。 - -对于此类「精确覆盖」问题,换个角度也可以看做「组合问题」。 - -通常有几种做法:`DFS`、剪枝 `DFS`、二进制枚举、模拟退火、`DLX`。 - -其中一头一尾解法过于简单和困难,有兴趣的同学自行了解与实现。 - ---- - -### 基本分析 - -根据题意,可以将本题看做一类特殊的「数独问题」:在给定的 `arr` 字符数组中选择,尽可能多的覆盖一个 $1 * 26$ 的矩阵。 +根据题意,可以将本题看做一类特殊的「数独问题」:在给定的 `arr` 字符数组中选择,尽可能多的覆盖一个 $1 \times 26$ 的矩阵。 对于此类「精确覆盖」问题,换个角度也可以看做「组合问题」。 diff --git "a/LeetCode/1721-1730/1723. \345\256\214\346\210\220\346\211\200\346\234\211\345\267\245\344\275\234\347\232\204\346\234\200\347\237\255\346\227\266\351\227\264\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/1721-1730/1723. \345\256\214\346\210\220\346\211\200\346\234\211\345\267\245\344\275\234\347\232\204\346\234\200\347\237\255\346\227\266\351\227\264\357\274\210\345\233\260\351\232\276\357\274\211.md" index 1b13b767..6fd6e9d5 100644 --- "a/LeetCode/1721-1730/1723. \345\256\214\346\210\220\346\211\200\346\234\211\345\267\245\344\275\234\347\232\204\346\234\200\347\237\255\346\227\266\351\227\264\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/1721-1730/1723. \345\256\214\346\210\220\346\211\200\346\234\211\345\267\245\344\275\234\347\232\204\346\234\200\347\237\255\346\227\266\351\227\264\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -38,8 +38,8 @@ Tag : 「DFS」、「模拟退火」、「启发式搜索」、「随机化」 ``` 提示: -* 1 <= k <= jobs.length <= 12 -* 1 <= jobs[i] <= $10^7$ +* $1 <= k <= jobs.length <= 12$ +* $1 <= jobs[i] <= 10^7$ --- @@ -185,6 +185,8 @@ class Solution { 2. 如果温度下降(交换后的序列更优),进入下一次迭代 3. 如果温度上升(交换前的序列更优),以「一定的概率」恢复现场(再交换回来) +> 对于一个能够运用模拟退火求解的问题,最核心的是如何实现 `calc` 方法(即如何定义一个具体方案的得分),其余均为模板内容。 + 代码: ```Java class Solution { @@ -249,18 +251,23 @@ class Solution { } } ``` +* 时间复杂度:启发式搜索不讨论时空复杂度 +* 空间复杂度:启发式搜索不讨论时空复杂度 --- ### 我猜你问 **Q0. 模拟退火有何风险?** + 随机算法,会面临 `WA` 和 `TLE` 风险。 **Q1. 模拟退火中的参数如何敲定的?** + 根据经验猜的,然后提交。根据结果是 `WA` 还是 `TLE` 来决定之后的调参方向。如果是 `WA` 说明部分数据落到了「局部最优」或者尚未达到「全局最优」。 **Q2. 参数如何调整?** + 如果是 `WA` 了,一般我是优先调大 fa 参数,使降温变慢,来变相增加迭代次数;如果是 `TLE` 了,一般是优先调小 fa 参数,使降温变快,减小迭代次数。总迭代参数 `N` 也是同理。 可以简单理解调大 fa 代表将「大步」改为「baby step」,防止越过全局最优,同时增加总执行步数。 @@ -291,7 +298,6 @@ class Solution { **本质上,我们并没有主动的否决某些方案(也就是我们并没有改动递归树),我们只是调整了搜索顺序来剪枝掉了一些「必然不是最优」的搜索路径。** - --- ### 最后 diff --git "a/LeetCode/761-770/764. \346\234\200\345\244\247\345\212\240\345\217\267\346\240\207\345\277\227\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/761-770/764. \346\234\200\345\244\247\345\212\240\345\217\267\346\240\207\345\277\227\357\274\210\344\270\255\347\255\211\357\274\211.md" new file mode 100644 index 00000000..46339816 --- /dev/null +++ "b/LeetCode/761-770/764. \346\234\200\345\244\247\345\212\240\345\217\267\346\240\207\345\277\227\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -0,0 +1,152 @@ +### 题目描述 + +这是 LeetCode 上的 **[764. 最大加号标志](https://leetcode.cn/problems/largest-plus-sign/solution/by-ac_oier-q932/)** ,难度为 **中等**。 + +Tag : 「模拟」、「预处理」 + + + +在一个 $n \times n$ 的矩阵 `grid` 中,除了在数组 `mines` 中给出的元素为 `0`,其他每个元素都为 `1`。$mines[i] = [x_i, y_i]$ 表示 $grid[x_i][y_i] = 0$。 + +返回  `grid` 中包含 `1` 的最大的 轴对齐 加号标志的阶数 。如果未找到加号标志,则返回 `0` 。 + +一个 `k` 阶由 `1` 组成的 “轴对称”加号标志 具有中心网格 $grid[r][c] = 1$ ,以及 `4` 个从中心向上、向下、向左、向右延伸,长度为 `k-1`,由 `1` 组成的臂。注意,只有加号标志的所有网格要求为 `1` ,别的网格可能为 `0` 也可能为 `1` 。 + +示例 1: +![](https://assets.leetcode.com/uploads/2021/06/13/plus1-grid.jpg) +``` +输入: n = 5, mines = [[4, 2]] + +输出: 2 + +解释: 在上面的网格中,最大加号标志的阶只能是2。一个标志已在图中标出。 +``` +示例 2: +![](https://assets.leetcode.com/uploads/2021/06/13/plus2-grid.jpg) +``` +输入: n = 1, mines = [[0, 0]] + +输出: 0 + +解释: 没有加号标志,返回 0 。 +``` + +提示: +* $1 <= n <= 500$ +* $1 <= mines.length <= 5000$ +* $0 <= x_i, y_i < n$ +* 每一对 $(x_i, y_i)$ 都 不重复 + +--- + +### 预处理 + 模拟 + +假设点 $(x, y)$ 能够取得最大长度 $k$,我们知道 $k$ 取决于以点 $(x, y)$ 为起点,四联通方向中「最短的连续 $1$ 的长度」。 + +基于此,我们可以建立四个大小为 $n \times n$ 的矩阵(二维数组)`a`、`b`、`c` 和 `d` 分别代表四个方向连续 $1$ 的前缀数: + +![image.png](https://pic.leetcode.cn/1667958744-jszheo-image.png) + +数据范围为 $500$,预处理前缀数组复杂度为 $O(n^2)$,统计答案复杂度为 $O(n^2)$,时间复杂度没有问题。 + +再考虑空间,建立四个方向的前缀数组所需空间为 $500 \times 500 \times 4 = 10^6$,即使加上原矩阵 `g` 也不会有 `MLE` 风险,空间复杂度也没有问题。 + +Java 代码: +```Java +class Solution { + public int orderOfLargestPlusSign(int n, int[][] mines) { + int[][] g = new int[n + 10][n + 10]; + for (int i = 1; i <= n; i++) Arrays.fill(g[i], 1); + for (int[] info : mines) g[info[0] + 1][info[1] + 1] = 0; + int[][] a = new int[n + 10][n + 10], b = new int[n + 10][n + 10], c = new int[n + 10][n + 10], d = new int[n + 10][n + 10]; + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= n; j++) { + if (g[i][j] == 1) { + a[i][j] = a[i - 1][j] + 1; + b[i][j] = b[i][j - 1] + 1; + } + if (g[n + 1 - i][n + 1 - j] == 1) { + c[n + 1 - i][n + 1 - j] = c[n + 2 - i][n + 1 - j] + 1; + d[n + 1 - i][n + 1 - j] = d[n + 1 - i][n + 2 - j] + 1; + } + } + } + int ans = 0; + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= n; j++) { + ans = Math.max(ans, Math.min(Math.min(a[i][j], b[i][j]), Math.min(c[i][j], d[i][j]))); + } + } + return ans; + } +} +``` +TypeScript 代码: +```TypeScript +function orderOfLargestPlusSign(n: number, mines: number[][]): number { + function getMat(x: number, y: number, val: number): number[][] { + const ans = new Array>(x) + for (let i = 0; i < x; i++) ans[i] = new Array(y).fill(val) + return ans + } + const g = getMat(n + 10, n + 10, 1) + for (const info of mines) g[info[0] + 1][info[1] + 1] = 0 + const a = getMat(n + 10, n + 10, 0), b = getMat(n + 10, n + 10, 0), c = getMat(n + 10, n + 10, 0), d = getMat(n + 10, n + 10, 0) + for (let i = 1; i <= n; i++) { + for (let j = 1; j <= n; j++) { + if (g[i][j] == 1) { + a[i][j] = a[i - 1][j] + 1 + b[i][j] = b[i][j - 1] + 1 + } + if (g[n + 1 - i][n + 1 - j] == 1) { + c[n + 1 - i][n + 1 - j] = c[n + 2 - i][n + 1 - j] + 1 + d[n + 1 - i][n + 1 - j] = d[n + 1 - i][n + 2 - j] + 1 + } + } + } + let ans = 0 + for (let i = 1; i <= n; i++) { + for (let j = 1; j <= n; j++) { + ans = Math.max(ans, Math.min(Math.min(a[i][j], b[i][j]), Math.min(c[i][j], d[i][j]))) + } + } + return ans +} +``` +Python 代码: +```Python +class Solution: + def orderOfLargestPlusSign(self, n: int, mines: List[List[int]]) -> int: + g = [[1] * (n + 10) for _ in range(n + 10)] + for x, y in mines: + g[x + 1][y + 1] = 0 + a, b, c, d = [[0] * (n + 10) for _ in range(n + 10)], [[0] * (n + 10) for _ in range(n + 10)], [[0] * (n + 10) for _ in range(n + 10)], [[0] * (n + 10) for _ in range(n + 10)] + for i in range(1, n + 1): + for j in range(1, n + 1): + if g[i][j] == 1: + a[i][j] = a[i - 1][j] + 1 + b[i][j] = b[i][j - 1] + 1 + if g[n + 1 - i][n + 1 - j] == 1: + c[n + 1 - i][n + 1 - j] = c[n + 2 - i][n + 1 - j] + 1 + d[n + 1 - i][n + 1 - j] = d[n + 1 - i][n + 2 - j] + 1 + ans = 0 + for i in range(1, n + 1): + for j in range(1, n + 1): + ans = max(ans, min(min(a[i][j], b[i][j]), min(c[i][j], d[i][j]))) + return ans +``` +* 时间复杂度:$O(n^2)$ +* 空间复杂度:$O(n^2)$ + +--- + +### 最后 + +这是我们「刷穿 LeetCode」系列文章的第 `No.764` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 + +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 + +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 + +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 + diff --git "a/LeetCode/861-870/864. \350\216\267\345\217\226\346\211\200\346\234\211\351\222\245\345\214\231\347\232\204\346\234\200\347\237\255\350\267\257\345\276\204\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/861-870/864. \350\216\267\345\217\226\346\211\200\346\234\211\351\222\245\345\214\231\347\232\204\346\234\200\347\237\255\350\267\257\345\276\204\357\274\210\345\233\260\351\232\276\357\274\211.md" new file mode 100644 index 00000000..38a085c1 --- /dev/null +++ "b/LeetCode/861-870/864. \350\216\267\345\217\226\346\211\200\346\234\211\351\222\245\345\214\231\347\232\204\346\234\200\347\237\255\350\267\257\345\276\204\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -0,0 +1,215 @@ +### 题目描述 + +这是 LeetCode 上的 **[864. 获取所有钥匙的最短路径](https://leetcode.cn/problems/shortest-path-to-get-all-keys/solution/by-ac_oier-5gxc/)** ,难度为 **困难**。 + +Tag : 「BFS」、「状态压缩」 + + + +给定一个二维网格 grid ,其中: + +* `'.'` 代表一个空房间 +* `'#'` 代表一堵墙 +* `'@'` 是起点 +* 小写字母代表钥匙 +* 大写字母代表锁 + +我们从起点开始出发,一次移动是指向四个基本方向之一行走一个单位空间。我们不能在网格外面行走,也无法穿过一堵墙。如果途经一个钥匙,我们就把它捡起来。除非我们手里有对应的钥匙,否则无法通过锁。 + +假设 `k` 为 钥匙/锁 的个数,且满足 $1 <= k <= 6$,字母表中的前 `k` 个字母在网格中都有自己对应的一个小写和一个大写字母。换言之,每个锁有唯一对应的钥匙,每个钥匙也有唯一对应的锁。另外,代表钥匙和锁的字母互为大小写并按字母顺序排列。 + +返回获取所有钥匙所需要的移动的最少次数。如果无法获取所有钥匙,返回 `-1` 。 + +示例 1: +![](https://assets.leetcode.com/uploads/2021/07/23/lc-keys2.jpg) +``` +输入:grid = ["@.a.#","###.#","b.A.B"] + +输出:8 + +解释:目标是获得所有钥匙,而不是打开所有锁。 +``` +示例 2: +![](https://assets.leetcode.com/uploads/2021/07/23/lc-key2.jpg) +``` +输入:grid = ["@..aA","..B#.","....b"] + +输出:6 +``` +示例 3: +![](https://assets.leetcode.com/uploads/2021/07/23/lc-keys3.jpg) +``` +输入: grid = ["@Aa"] + +输出: -1 +``` + +提示: +* $m == grid.length$ +* $n == grid[i].length$ +* $1 <= m, n <= 30$ +* `grid[i][j]` 只含有 `'.'`,`'#'`, `'@'`, `'a'-'f'` 以及 `'A'-'F'` +* 钥匙的数目范围是 $[1, 6]$ +* 每个钥匙都对应一个 不同 的字母 +* 每个钥匙正好打开一个对应的锁 + +--- + +### BFS + 状态压缩 + +**一道常规的 `BFS` 运用题,只不过需要在 `BFS` 过程中记录收集到的钥匙状态。** + +利用「钥匙数量不超过 $6$,并按字母顺序排列」,我们可以使用一个 `int` 类型二进制数 `state` 来代指当前收集到钥匙情况: + +* 若 `state` 的二进制中的第 $k$ 位为 `1`,代表当前种类编号为 $k$ 的钥匙 **已被收集**,后续移动若遇到对应的锁则 **能通过** +* 若 `state` 的二进制中的第 $k$ 位为 `0`,代表当前种类编号为 $k$ 的钥匙 **未被收集**,后续移动若遇到对应的锁则 **无法通过** + +其中「钥匙种类编号」则按照小写字母先后顺序,从 $0$ 开始进行划分对应:即字符为 `a` 的钥匙编号为 `0`,字符为 `b` 的钥匙编号为 `1`,字符为 `c` 的钥匙编号为 `2` ... + +当使用了这样的「状态压缩」技巧后,我们可以很方便通过「位运算」进行 **钥匙检测** 和 **更新钥匙收集状态**: + +* 钥匙检测:`(state >> k) & 1`,若返回 `1` 说明第 $k$ 位为 `1`,当前持有种类编号为 `k` 的钥匙 +* 更新钥匙收集状态:`state |= 1 << k`,将 `state` 的第 $k$ 位设置为 `1`,代表当前新收集到种类编号为 `k` 的钥匙 + +搞明白如何记录当前收集到的钥匙状态后,剩下的则是常规 `BFS` 过程: + +1. 起始遍历一次棋盘,找到起点位置,并将其进行入队,队列维护的是 $(x, y, state)$ 三元组状态(其中 $(x, y)$ 代表当前所在的棋盘位置,$state$ 代表当前的钥匙收集情况) + 同时统计整个棋盘所包含的钥匙数量 `cnt`,并使用 数组/哈希表 记录到达每个状态所需要消耗的最小步数 `step` + +2. 进行四联通方向的 `BFS`,转移过程中需要注意「遇到锁时,必须有对应钥匙才能通过」&「遇到钥匙时,需要更新对应的 `state` 再进行入队」 + +3. 当 `BFS` 过程中遇到 `state = (1 << cnt) - 1` 时,代表所有钥匙均被收集完成,可结束搜索 + +Java 代码: +```Java +class Solution { + static int N = 35, K = 10, INF = 0x3f3f3f3f; + static int[][][] dist = new int[N][N][1 << K]; + static int[][] dirs = new int[][]{{1,0},{-1,0},{0,1},{0,-1}}; + public int shortestPathAllKeys(String[] g) { + int n = g.length, m = g[0].length(), cnt = 0; + Deque d = new ArrayDeque<>(); + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + Arrays.fill(dist[i][j], INF); + char c = g[i].charAt(j); + if (c == '@') { + d.addLast(new int[]{i, j, 0}); + dist[i][j][0] = 0; + } else if (c >= 'a' && c <= 'z') cnt++; + } + } + while (!d.isEmpty()) { + int[] info = d.pollFirst(); + int x = info[0], y = info[1], cur = info[2], step = dist[x][y][cur]; + for (int[] di : dirs) { + int nx = x + di[0], ny = y + di[1]; + if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue; + char c = g[nx].charAt(ny); + if (c == '#') continue; + if ((c >= 'A' && c <= 'Z') && (cur >> (c - 'A') & 1) == 0) continue; + int ncur = cur; + if (c >= 'a' && c <= 'z') ncur |= 1 << (c - 'a'); + if (ncur == (1 << cnt) - 1) return step + 1; + if (step + 1 >= dist[nx][ny][ncur]) continue; + dist[nx][ny][ncur] = step + 1; + d.addLast(new int[]{nx, ny, ncur}); + } + } + return -1; + } +} +``` +TypeScript 代码: +```TypeScript +function shortestPathAllKeys(g: string[]): number { + const dirs = [[1,0],[-1,0],[0,1],[0,-1]] + let n = g.length, m = g[0].length, cnt = 0 + const dist = new Array>>() + for (let i = 0; i < n; i++) { + dist[i] = new Array>(m) + for (let j = 0; j < m; j++) { + dist[i][j] = new Array(1 << 10).fill(0x3f3f3f3f) + } + } + const d = [] + for (let i = 0; i < n; i++) { + for (let j = 0; j < m; j++) { + if (g[i][j] == '@') { + d.push([i, j, 0]); dist[i][j][0] = 0 + } else if (g[i][j] >= 'a' && g[i][j] <= 'z') cnt++ + } + } + while (d.length > 0) { + const info = d.shift() + const x = info[0], y = info[1], cur = info[2], step = dist[x][y][cur] + for (const di of dirs) { + const nx = x + di[0], ny = y + di[1] + if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue + const c = g[nx][ny] + if (c == '#') continue + if ('A' <= c && c <= 'Z' && ((cur >> (c.charCodeAt(0) - 'A'.charCodeAt(0)) & 1) == 0)) continue + let ncur = cur + if ('a' <= c && c <= 'z') ncur |= 1 << (c.charCodeAt(0) - 'a'.charCodeAt(0)) + if (ncur == (1 << cnt) - 1) return step + 1 + if (step + 1 >= dist[nx][ny][ncur]) continue + d.push([nx, ny, ncur]) + dist[nx][ny][ncur] = step + 1 + } + } + return -1 +} +``` +Python3 代码: +```Python +class Solution: + def shortestPathAllKeys(self, g: List[str]) -> int: + dirs = [[0,1], [0,-1], [1,0], [-1,0]] + n, m, cnt = len(g), len(g[0]), 0 + dist = defaultdict(lambda : 0x3f3f3f3f) + for i in range(n): + for j in range(m): + c = g[i][j] + if c == '@': + d = deque([(i, j, 0)]) + dist[(i, j, 0)] = 0 + elif 'a' <= c <= 'z': + cnt += 1 + while d: + x, y, cur = d.popleft() + step = dist[(x, y, cur)] + for di in dirs: + nx, ny = x + di[0], y + di[1] + if nx < 0 or nx >= n or ny < 0 or ny >= m: + continue + c = g[nx][ny] + if c == '#': + continue + if 'A' <= c <= 'Z' and (cur >> (ord(c) - ord('A')) & 1) == 0: + continue + ncur = cur + if 'a' <= c <= 'z': + ncur |= (1 << (ord(c) - ord('a'))) + if ncur == (1 << cnt) - 1: + return step + 1 + if step + 1 >= dist[(nx, ny, ncur)]: + continue + dist[(nx, ny, ncur)] = step + 1 + d.append((nx, ny, ncur)) + return -1 +``` +* 时间复杂度:$O(n \times m \times 2^k)$ +* 空间复杂度:$O(n \times m \times 2^k)$ + +--- + +### 最后 + +这是我们「刷穿 LeetCode」系列文章的第 `No.864` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 + +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 + +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 + +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 +