2
2
3
3
这是 LeetCode 上的 ** [ 433. 最小基因变化] ( https://leetcode-cn.com/problems/minimum-genetic-mutation/solution/by-ac_oier-74b4/ ) ** ,难度为 ** 中等** 。
4
4
5
- Tag : 「BFS」、「双向 BFS」、「A* 算法」、「启发式搜索」
5
+ Tag : 「BFS」、「双向 BFS」、「图论 DFS」、「 A* 算法」、「启发式搜索」
6
6
7
7
8
8
@@ -49,7 +49,7 @@ Tag : 「BFS」、「双向 BFS」、「A* 算法」、「启发式搜索」
49
49
50
50
为了方便,我们令 $S = start$、 $T = end$,将每个基因序列视为「状态」。
51
51
52
- 容易想到使用 ` BFS ` 进行求解,并使用「哈希表」记录到达某个状态所消耗的步数(同时为了快速判断某个状态是否合法,我们使用 ` Set ` 结构对 $back [ i] $ 进行转存)。
52
+ 容易想到使用 ` BFS ` 进行求解,并使用「哈希表」记录到达某个状态所消耗的步数(同时为了快速判断某个状态是否合法,我们使用 ` Set ` 结构对 $bank [ i] $ 进行转存)。
53
53
54
54
起始将 ` S ` 加入队列,并更新到达 ` S ` 所使用的步数为 $0$,然后进行常规的 ` BFS ` 过程:每次取出队头元素,尝试替换当前状态的某一位,来得到新的状态(限定新状态必须合法,即必须出现在 ` Set ` 中),如果新状态合法并且没有在记录步数的哈希表中出现过,则将新状态入队并更新得到新状态所用步数,否则丢弃新状态。
55
55
@@ -91,7 +91,7 @@ class Solution {
91
91
}
92
92
}
93
93
```
94
- * 时间复杂度:令 $n$ 为 ` bank ` 的数组长度(合法状态数),将 ` back ` 存入 ` Set ` 结构复杂度为 $O(n)$,每个状态经过一步操作最多拓展出 $C = 32$ 个新基因(共有 $8$ 个位置,每个位置有 $4$ 个选择),` BFS ` 过程复杂度为 $O(C * n)$。整体复杂度为 $O(C * n)$
94
+ * 时间复杂度:令 $n$ 为 ` bank ` 的数组长度(合法状态数),将 ` bank ` 存入 ` Set ` 结构复杂度为 $O(n)$,每个状态经过一步操作最多拓展出 $C = 32$ 个新基因(共有 $8$ 个位置,每个位置有 $4$ 个选择),` BFS ` 过程复杂度为 $O(C * n)$。整体复杂度为 $O(C * n)$
95
95
* 空间复杂度:$O(n)$
96
96
97
97
---
@@ -155,7 +155,7 @@ class Solution {
155
155
}
156
156
}
157
157
```
158
- * 时间复杂度:令 $n$ 为 ` bank ` 的数组长度(合法状态数),将 ` back ` 存入 ` Set ` 结构复杂度为 $O(n)$,每个状态经过一步操作最多拓展出 $C = 32$ 个新基因(共有 $8$ 个位置,每个位置有 $4$ 个选择),` BFS ` 过程复杂度为 $O(C * n)$。整体复杂度为 $O(C * n)$
158
+ * 时间复杂度:令 $n$ 为 ` bank ` 的数组长度(合法状态数),将 ` bank ` 存入 ` Set ` 结构复杂度为 $O(n)$,每个状态经过一步操作最多拓展出 $C = 32$ 个新基因(共有 $8$ 个位置,每个位置有 $4$ 个选择),` BFS ` 过程复杂度为 $O(C * n)$。整体复杂度为 $O(C * n)$
159
159
* 空间复杂度:$O(n)$
160
160
161
161
---
@@ -168,7 +168,7 @@ class Solution {
168
168
169
169
具体的,我们使用优先队列(堆)维护所有的状态,每次优先「启发值 = 理论最小转换步数」的状态进行优先出队拓展。
170
170
171
- 对「A* 算法」不了解的同学可以看前置 🧀:[ 发挥 A* 算法最大价值的关键点] ( https%3A //mp.weixin.qq.com/s?__biz%3DMzU4NDE3MTEyMA%3D%3D%26mid%3D2247489588%26idx%3D1%26sn%3D479e4c0627247ab7e20af7909f2a8b64 ) 。
171
+ 对「A* 算法」不了解的同学可以看前置 🧀:[ 发挥 A* 算法最大价值的关键点] ( https: //mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247489588&idx=1&sn=479e4c0627247ab7e20af7909f2a8b64 ) 。
172
172
173
173
代码:
174
174
``` Java
@@ -221,6 +221,80 @@ class Solution {
221
221
222
222
---
223
223
224
+ ### 建图 + DFS
225
+
226
+ ** 由 ` S ` 和 $bank[ i] $ 组成合法点集,且点集中任意两点之间存在无向边的充要条件是:点 $u$ 和点 $v$ 所代表的字符中,仅有一个位置字符不同。**
227
+
228
+ 因此我们可以将所有的点存入 ` list ` 中,假设 ` list ` 长度为 $n$。同时为了方便,我们人为确保 ` S ` 出现在头部(点编号为 $1$),` T ` 出现在尾部(点编号为 $n$)。
229
+
230
+ 遍历 ` list ` 进行建图(对于两字符串中仅有一位置不同的点进行连边操作),然后跑一遍从 $1$ 到 $n$ 的 ` DFS ` 。
231
+
232
+ 由于图中可能有环或无解,因此必须「设定一个最大搜索深度」并增加「最优解剪枝」,确保搜索过程结束。
233
+
234
+ 最大搜索深度的设定可以利用反证法:如果 ` S ` 能够到达 ` T ` ,那么最优路径中必然不存在环(否则可以把环去掉,得到一条更短的路径),即最优路径所经过的点的数量必然不超过 $n$。
235
+
236
+ 代码:
237
+ ``` Java
238
+ class Solution {
239
+ int N = 15 , M = 15 * 15 * 2 + 50 , idx = 0 , loc = 1 ;
240
+ int [] he = new int [N ], e = new int [M ], ne = new int [M ];
241
+ int n, ans;
242
+ void add (int a , int b ) {
243
+ e[idx] = b;
244
+ ne[idx] = he[a];
245
+ he[a] = idx++ ;
246
+ }
247
+ void dfs (int u , int fa , int depth ) {
248
+ if (depth >= ans) return ; // 最优解剪枝
249
+ if (u == n) {
250
+ ans = depth;
251
+ return ;
252
+ }
253
+ for (int i = he[u]; i != - 1 ; i = ne[i]) {
254
+ int j = e[i];
255
+ if (j == fa) continue ;
256
+ dfs(j, u, depth + 1 );
257
+ }
258
+ }
259
+ public int minMutation (String S , String T , String [] bank ) {
260
+ List<String > list = new ArrayList<> ();
261
+ list. add(S );
262
+ boolean ok = false ;
263
+ for (String s : bank) {
264
+ if (s. equals(S )) continue ;
265
+ if (s. equals(T )) {
266
+ ok = true ;
267
+ continue ;
268
+ }
269
+ list. add(s);
270
+ }
271
+ if (! ok) return - 1 ;
272
+ list. add(T );
273
+ n = list. size();
274
+ ans = n;
275
+ Arrays . fill(he, - 1 );
276
+ for (int i = 0 ; i < n; i++ ) {
277
+ for (int j = 0 ; j < n; j++ ) {
278
+ if (i == j) continue ;
279
+ int cnt = 0 ;
280
+ for (int k = 0 ; k < 8 && cnt <= 1 ; k++ ) {
281
+ if (list. get(i). charAt(k) != list. get(j). charAt(k)) cnt++ ;
282
+ }
283
+ if (cnt == 1 ) {
284
+ add(i + 1 , j + 1 ); add(j + 1 , i + 1 );
285
+ }
286
+ }
287
+ }
288
+ dfs(1 , - 1 , 0 );
289
+ return ans == n ? - 1 : ans;
290
+ }
291
+ }
292
+ ```
293
+ * 时间复杂度:令 ` bank ` 的长度为 $n$(即点集的数量级为 $n$),预处理出 ` list ` 的复杂度为 $O(n)$;建图操作的复杂度为 $O(C * n^2)$,其中 $C = 8$ 基因序列长度;` DFS ` 过程由于设定了最大搜索深度,复杂度为 $O(n^2)$。整体复杂度为 $O(C * n^2)$
294
+ * 空间复杂度:最坏情况下为完全图,复杂度为 $O(n^2)$
295
+
296
+ ---
297
+
224
298
### 最后
225
299
226
300
这是我们「刷穿 LeetCode」系列文章的第 ` No.433 ` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
0 commit comments