Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 49588e3

Browse files
Merge pull request SharingSource#24 from SharingSource/ac_oier
✨feat: Add 773 & Modify 752
2 parents de44d36 + d1fb6ca commit 49588e3

File tree

4 files changed

+262
-2
lines changed

4 files changed

+262
-2
lines changed

‎Index/启发式搜索.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
| ------------------------------------------------------------ | ------------------------------------------------------------ | ---- | -------- |
33
| [127. 单词接龙](https://leetcode-cn.com/problems/word-ladder/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/word-ladder/solution/gong-shui-san-xie-ru-he-shi-yong-shuang-magjd/) | 困难 | 🤩🤩🤩🤩🤩 |
44
| [752. 打开转盘锁](https://leetcode-cn.com/problems/open-the-lock/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/open-the-lock/solution/gong-shui-san-xie-yi-ti-shuang-jie-shuan-wyr9/) | 中等 | 🤩🤩🤩🤩 |
5+
| [773. 滑动谜题](https://leetcode-cn.com/problems/sliding-puzzle/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/sliding-puzzle/solution/gong-shui-san-xie-fa-hui-a-suan-fa-zui-d-3go8/) | 困难 | 🤩🤩🤩🤩 |
56
| [1239. 串联字符串的最大长度](https://leetcode-cn.com/problems/maximum-length-of-a-concatenated-string-with-unique-characters/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/maximum-length-of-a-concatenated-string-with-unique-characters/solution/gong-shui-san-xie-yi-ti-san-jie-jian-zhi-nfeb/) | 中等 | 🤩🤩🤩🤩🤩 |
67
| [1723. 完成所有工作的最短时间](https://leetcode-cn.com/problems/find-minimum-time-to-finish-all-jobs/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/find-minimum-time-to-finish-all-jobs/solution/gong-shui-san-xie-yi-ti-shuang-jie-jian-4epdd/) | 困难 | 🤩🤩🤩🤩 |
78

‎Index/图论 BFS.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
| [127. 单词接龙](https://leetcode-cn.com/problems/word-ladder/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/word-ladder/solution/gong-shui-san-xie-ru-he-shi-yong-shuang-magjd/) | 困难 | 🤩🤩🤩🤩🤩 |
44
| [403. 青蛙过河](https://leetcode-cn.com/problems/frog-jump/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/frog-jump/solution/gong-shui-san-xie-yi-ti-duo-jie-jiang-di-74fw/) | 困难 | 🤩🤩🤩🤩🤩 |
55
| [752. 打开转盘锁](https://leetcode-cn.com/problems/open-the-lock/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/open-the-lock/solution/gong-shui-san-xie-yi-ti-shuang-jie-shuan-wyr9/) | 中等 | 🤩🤩🤩🤩 |
6+
| [773. 滑动谜题](https://leetcode-cn.com/problems/sliding-puzzle/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/sliding-puzzle/solution/gong-shui-san-xie-fa-hui-a-suan-fa-zui-d-3go8/) | 困难 | 🤩🤩🤩🤩 |
67

‎LeetCode/751-760/752. 打开转盘锁(中等).md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ Tag : 「双向 BFS」、「启发式搜索」、「AStar 算法」、「IDAStar
6060

6161
「127. 单词接龙」原题链接在 [这里](https://leetcode-cn.com/problems/word-ladder/),相关题解在 [这里](https://leetcode-cn.com/problems/word-ladder/solution/gong-shui-san-xie-ru-he-shi-yong-shuang-magjd/)
6262

63-
回到本题,根据题意,可以确定这是一个「最短路」问题。
63+
回到本题,根据题意,可以确定这是一个「最短路/最小步数」问题。
6464

6565
此类问题,通常我们会使用「BFS」求解,但朴素的 BFS 通常会带来搜索空间爆炸问题。
6666

@@ -148,7 +148,7 @@ class Solution {
148148
Deque<String> d1 = new ArrayDeque<>(), d2 = new ArrayDeque<>();
149149
/*
150150
* m1 和 m2 分别记录两个方向出现的状态是经过多少次转换而来
151-
* e.g.s
151+
* e.g
152152
* m1 = {"1000":1} 代表 "1000" 由 s="0000" 替换 1 次字符而来
153153
* m2 = {"9999":3} 代表 "9999" 由 t="9996" 替换 3 次字符而来
154154
*/
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[773. 滑动谜题](https://leetcode-cn.com/problems/sliding-puzzle/solution/gong-shui-san-xie-fa-hui-a-suan-fa-zui-d-3go8/)** ,难度为 **困难**
4+
5+
Tag : 「BFS」、「最小步数」、「AStar 算法」、「启发式搜索」
6+
7+
8+
9+
在一个 2 x 3 的板上(board)有 5 块砖瓦,用数字 1~5 来表示, 以及一块空缺用 0 来表示.
10+
11+
一次移动定义为选择 0 与一个相邻的数字(上下左右)进行交换.
12+
13+
最终当板 board 的结果是 [[1,2,3],[4,5,0]] 谜板被解开。
14+
15+
给出一个谜板的初始状态,返回最少可以通过多少次移动解开谜板,如果不能解开谜板,则返回 -1 。
16+
17+
示例:
18+
```
19+
输入:board = [[1,2,3],[4,0,5]]
20+
输出:1
21+
解释:交换 0 和 5 ,1 步完成
22+
```
23+
```
24+
输入:board = [[1,2,3],[5,4,0]]
25+
输出:-1
26+
解释:没有办法完成谜板
27+
```
28+
```
29+
输入:board = [[4,1,2],[5,0,3]]
30+
输出:5
31+
解释:
32+
最少完成谜板的最少移动次数是 5 ,
33+
一种移动路径:
34+
尚未移动: [[4,1,2],[5,0,3]]
35+
移动 1 次: [[4,1,2],[0,5,3]]
36+
移动 2 次: [[0,1,2],[4,5,3]]
37+
移动 3 次: [[1,0,2],[4,5,3]]
38+
移动 4 次: [[1,2,0],[4,5,3]]
39+
移动 5 次: [[1,2,3],[4,5,0]]
40+
```
41+
```
42+
输入:board = [[3,2,4],[1,5,0]]
43+
输出:14
44+
```
45+
提示:
46+
* board 是一个如上所述的 2 x 3 的数组.
47+
* board[i][j] 是一个 [0, 1, 2, 3, 4, 5] 的排列.
48+
49+
---
50+
51+
### 基本分析
52+
53+
这是八数码问题的简化版:将 3ドル * 3$ 变为 2ドル * 3,ドル同时将「输出路径」变为「求最小步数」。
54+
55+
通常此类问题可以使用「BFS」、「AStar 算法」、「康拓展开」进行求解。
56+
57+
由于问题简化到了 2ドル * 3,ドル我们使用前两种解法即可。
58+
59+
---
60+
61+
### BFS
62+
63+
为了方便,将原来的二维矩阵转成字符串(一维矩阵)进行处理。
64+
65+
这样带来的好处直接可以作为哈希 `Key` 使用,也可以很方便进行「二维坐标」与「一维下标」的转换。
66+
67+
由于固定是 2ドル * 3$ 的格子,因此任意的合法二维坐标 $(x, y)$ 和对应一维下标 $idx$ 可通过以下转换:
68+
69+
* $idx = x * 3 + y$
70+
* $x = idx / 3,y = idx \% 3$
71+
72+
其余的就是常规的 `BFS` 过程了。
73+
74+
代码:
75+
```Java []
76+
class Solution {
77+
class Node {
78+
String str;
79+
int x, y;
80+
Node(String _str, int _x, int _y) {
81+
str = _str; x = _x; y = _y;
82+
}
83+
}
84+
int n = 2, m = 3;
85+
String s, e;
86+
int x, y;
87+
public int slidingPuzzle(int[][] board) {
88+
s = "";
89+
e = "123450";
90+
for (int i = 0; i < n; i++) {
91+
for (int j = 0; j < m; j++) {
92+
s += board[i][j];
93+
if (board[i][j] == 0) {
94+
x = i; y = j;
95+
}
96+
}
97+
}
98+
int ans = bfs();
99+
return ans;
100+
}
101+
int[][] dirs = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
102+
int bfs() {
103+
Deque<Node> d = new ArrayDeque<>();
104+
Map<String, Integer> map = new HashMap<>();
105+
Node root = new Node(s, x, y);
106+
d.addLast(root);
107+
map.put(s, 0);
108+
while (!d.isEmpty()) {
109+
Node poll = d.pollFirst();
110+
int step = map.get(poll.str);
111+
if (poll.str.equals(e)) return step;
112+
int dx = poll.x, dy = poll.y;
113+
for (int[] di : dirs) {
114+
int nx = dx + di[0], ny = dy + di[1];
115+
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
116+
String nStr = update(poll.str, dx, dy, nx, ny);
117+
if (map.containsKey(nStr)) continue;
118+
Node next = new Node(nStr, nx, ny);
119+
d.addLast(next);
120+
map.put(nStr, step + 1);
121+
}
122+
}
123+
return -1;
124+
}
125+
String update(String cur, int i, int j, int p, int q) {
126+
char[] cs = cur.toCharArray();
127+
char tmp = cs[i * m + j];
128+
cs[i * m + j] = cs[p * m + q];
129+
cs[p * m + q] = tmp;
130+
return String.valueOf(cs);
131+
}
132+
}
133+
```
134+
135+
---
136+
137+
### A\* 算法
138+
139+
可以直接根据本题规则来设计 A* 的「启发式函数」。
140+
141+
比如对于两个状态 `a``b` 可直接计算出「理论最小转换次数」:**所有位置的数值「所在位置」与「目标位置」的曼哈顿距离之和(即横纵坐标绝对值之和)**
142+
143+
注意,我们只需要计算「非空格」位置的曼哈顿距离即可,因为空格的位置会由其余数字占掉哪些位置而唯一确定。
144+
145+
**A\* 求最短路的正确性问题:由于我们衡量某个状态 `str` 的估值是以目标字符串 `e=123450` 为基准,因此我们只能确保 `e` 出队时为「距离最短」,而不能确保中间节点出队时「距离最短」,因此我们不能单纯根据某个节点是否「曾经入队」而决定是否入队,还要结合当前节点的「最小距离」是否被更新而决定是否入队。**
146+
147+
这一点十分关键,在代码层面上体现在 `map.get(nStr) > step + 1` 的判断上。
148+
149+
**我们知道,A\* 在有解的情况下,才会发挥「启发式搜索」的最大价值,因此如果我们能够提前判断无解的情况,对 A\* 算法来说会是巨大的提升。**
150+
151+
而对于通用的 $N * N$ 数码问题,判定有解的一个充要条件是:**「逆序对」数量为偶数,如果不满足,必然无解,直接返回 $-1$ 即可。**
152+
153+
对该结论的充分性证明和必要性证明完全不在一个难度上,所以建议记住这个结论即可。
154+
155+
代码:
156+
```Java []
157+
class Solution {
158+
class Node {
159+
String str;
160+
int x, y;
161+
int val;
162+
Node(String _str, int _x, int _y, int _val) {
163+
str = _str; x = _x; y = _y; val = _val;
164+
}
165+
}
166+
int f(String str) {
167+
int ans = 0;
168+
char[] cs1 = str.toCharArray(), cs2 = e.toCharArray();
169+
for (int i = 0; i < n; i++) {
170+
for (int j = 0; j < m; j++) {
171+
// 跳过「空格」,计算其余数值的曼哈顿距离
172+
if (cs1[i * m + j] == '0' || cs2[i * m + j] == '0') continue;
173+
int cur = cs1[i * m + j], next = cs2[i * m + j];
174+
int xd = Math.abs((cur - 1) / 3 - (next - 1) / 3);
175+
int yd = Math.abs((cur - 1) % 3 - (next - 1) % 3);
176+
ans += (xd + yd);
177+
}
178+
}
179+
return ans;
180+
}
181+
int n = 2, m = 3;
182+
String s, e;
183+
int x, y;
184+
public int slidingPuzzle(int[][] board) {
185+
s = "";
186+
e = "123450";
187+
for (int i = 0; i < n; i++) {
188+
for (int j = 0; j < m; j++) {
189+
s += board[i][j];
190+
if (board[i][j] == 0) {
191+
x = i; y = j;
192+
}
193+
}
194+
}
195+
196+
// 提前判断无解情况
197+
if (!check(s)) return -1;
198+
199+
int[][] dirs = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
200+
Node root = new Node(s, x, y, f(s));
201+
PriorityQueue<Node> q = new PriorityQueue<>((a,b)->a.val-b.val);
202+
Map<String, Integer> map = new HashMap<>();
203+
q.add(root);
204+
map.put(s, 0);
205+
while (!q.isEmpty()) {
206+
Node poll = q.poll();
207+
int step = map.get(poll.str);
208+
if (poll.str.equals(e)) return step;
209+
int dx = poll.x, dy = poll.y;
210+
for (int[] di : dirs) {
211+
int nx = dx + di[0], ny = dy + di[1];
212+
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
213+
String nStr = update(poll.str, dx, dy, nx, ny);
214+
if (!map.containsKey(nStr) || map.get(nStr) > step + 1) {
215+
Node next = new Node(nStr, nx, ny, step + 1 + f(nStr));
216+
q.add(next);
217+
map.put(nStr, step + 1);
218+
}
219+
}
220+
}
221+
return 0x3f3f3f3f; // never
222+
}
223+
String update(String cur, int i, int j, int p, int q) {
224+
char[] cs = cur.toCharArray();
225+
char tmp = cs[i * m + j];
226+
cs[i * m + j] = cs[p * m + q];
227+
cs[p * m + q] = tmp;
228+
return String.valueOf(cs);
229+
}
230+
boolean check(String str) {
231+
char[] cs = str.toCharArray();
232+
List<Integer> list = new ArrayList<>();
233+
for (int i = 0; i < n * m; i++) {
234+
if (cs[i] != '0') list.add(cs[i] - '0');
235+
}
236+
int cnt = 0;
237+
for (int i = 0; i < list.size(); i++) {
238+
for (int j = i + 1; j < list.size(); j++) {
239+
if (list.get(i) < list.get(j)) cnt++;
240+
}
241+
}
242+
return cnt % 2 == 0;
243+
}
244+
}
245+
```
246+
247+
---
248+
249+
### 最后
250+
251+
这是我们「刷穿 LeetCode」系列文章的第 `No.773` 篇,系列开始于 2021年01月01日,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先将所有不带锁的题目刷完。
252+
253+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
254+
255+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
256+
257+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
258+

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /