@@ -87,6 +87,7 @@ \subsubsection{单队列}
87
87
for (size_t i = 0; i < s.word.size(); ++i) {
88
88
state_t new_state(s.word, s.step + 1);
89
89
for (char c = 'a' ; c <= 'z' ; c++) {
90
+ // 防止同字母替换
90
91
if (c == new_state.word[i]) continue;
91
92
92
93
swap(c, new_state.word[i]);
@@ -150,6 +151,7 @@ \subsubsection{双队列}
150
151
for (size_t i = 0; i < s.size(); ++i) {
151
152
string new_word(s);
152
153
for (char c = 'a' ; c <= 'z' ; c++) {
154
+ // 防止同字母替换
153
155
if (c == new_word[i]) continue;
154
156
155
157
swap(c, new_word[i]);
@@ -235,12 +237,11 @@ \subsubsection{描述}
235
237
\subsubsection {分析 }
236
238
跟 Word Ladder比,这题是求路径本身,不是路径长度,也是BFS,略微麻烦点。
237
239
238
- 这题跟普通的广搜有很大的不同,就是要输出所有路径,因此在记录前驱和判重地方与普通广搜略有不同 。
240
+ 求一条路径和求所有路径有很大的不同,求一条路径,每个状态节点只需要记录一个前驱即可;求所有路径时,有的状态节点可能有多个父节点,即要记录多个前驱 。
239
241
242
+ 如果当前路径长度已经超过当前最短路径长度,可以中止对该路径的处理,因为我们要找的是最短路径。
240
243
241
- \subsubsection {单队列 }
242
-
243
- 单队列无法求解所有路径。因为 需要一个 \fn {queue} 和 \fn {unordered_set} 的综合体,这是不可能的。
244
+ 单队列无法求解所有路径。因为 需要一个 \fn {queue} 和 \fn {unordered_set} 的综合体,这是不可能的。本题可以用双队列来求解。
244
245
245
246
246
247
\subsubsection {双队列 }
@@ -259,13 +260,16 @@ \subsubsection{双队列}
259
260
unordered_set<string> visited; // 判重
260
261
unordered_map<string, vector<string> > father; // DAG
261
262
263
+ int level = 0; // 层次
264
+
262
265
auto state_is_target = [&](const string &s) {return s == end;};
263
266
auto state_extend = [&](const string &s) {
264
267
unordered_set<string> result;
265
268
266
269
for (size_t i = 0; i < s.size(); ++i) {
267
270
string new_word(s);
268
271
for (char c = 'a' ; c <= 'z' ; c++) {
272
+ // 防止同字母替换
269
273
if (c == new_word[i]) continue;
270
274
271
275
swap(c, new_word[i]);
@@ -284,6 +288,11 @@ \subsubsection{双队列}
284
288
vector<vector<string> > result;
285
289
current.insert(start);
286
290
while (!current.empty()) {
291
+ ++ level;
292
+ // 如果当前路径长度已经超过当前最短路径长度,可以中止对该路径的
293
+ // 处理,因为我们要找的是最短路径
294
+ if (!result.empty() && level > result[0].size()) break;
295
+
287
296
// 1. 延迟加入visited, 这样才能允许两个父节点指向同一个子节点
288
297
// 2. 一股脑current 全部加入visited, 是防止本层前一个节点扩展
289
298
// 节点时,指向了本层后面尚未处理的节点,这条路径必然不是最短的
@@ -314,7 +323,18 @@ \subsubsection{双队列}
314
323
vector<vector<string> > &result) {
315
324
path.push_back(word);
316
325
if (word == start) {
317
- result.push_back(path);
326
+ if (!result.empty()) {
327
+ if (path.size() < result[0].size()) {
328
+ result.clear();
329
+ result.push_back(path);
330
+ } else if(path.size() == result[0].size()) {
331
+ result.push_back(path);
332
+ } else {
333
+ // throw exception
334
+ }
335
+ } else {
336
+ result.push_back(path);
337
+ }
318
338
reverse(result.back().begin(), result.back().end());
319
339
} else {
320
340
for (const auto& f : father[word]) {
@@ -327,6 +347,137 @@ \subsubsection{双队列}
327
347
\end {Code }
328
348
329
349
350
+ \subsubsection {图的广搜 }
351
+
352
+ 本题还可以看做是图上的广搜。给定了字典 \fn {dict},可以基于它画出一个无向图,表示单词之间可以互相转换。本题的本质就是已知起点和终点,在图上找出所有最短路径。
353
+
354
+ \begin {Code }
355
+ //LeetCode, Word Ladder II
356
+ // 时间复杂度O(n),空间复杂度O(n)
357
+ class Solution {
358
+ public:
359
+ vector<vector<string> > findLadders(const string& start,
360
+ const string &end, const unordered_set<string> &dict) {
361
+ const auto& g = build_graph(dict);
362
+ vector<path_node_t*> pool;
363
+ queue<path_node_t*> q; // 未处理的节点
364
+ // value 是所在层次
365
+ unordered_map<string, int> visited;
366
+
367
+ q.push(new_path_node(nullptr, start, 1, pool));
368
+ visited[start] = 1;
369
+
370
+ vector<vector<string>> result;
371
+ while (!q.empty()) {
372
+ path_node_t* node = q.front();
373
+ q.pop();
374
+
375
+ // 如果当前路径长度已经超过当前最短路径长度,
376
+ // 可以中止对该路径的处理,因为我们要找的是最短路径
377
+ if (!result.empty() && node->length > result[0].size()) break;
378
+
379
+ if (node->word == end) {
380
+ result.push_back(gen_path(node));
381
+ continue;
382
+ }
383
+ // 挪到了下面,只有尽早加入visited, 才能防止,同一层,
384
+ // 前面一个子节点扩展时,指向同层后面的节点
385
+ // visited[node->word] = node->length;
386
+
387
+ // 扩展节点
388
+ auto iter = g.find(node->word);
389
+ if (iter == g.end()) continue;
390
+
391
+ for (const auto& neighbor : iter->second) {
392
+ auto visited_iter = visited.find(neighbor);
393
+
394
+ if (visited_iter != visited.end()) {
395
+ if (visited_iter->second < node->length + 1) {
396
+ continue;
397
+ } else if (visited_iter->second > node->length + 1) {
398
+ // not possible, throw exception
399
+ // throw std::logic_error("not possible");
400
+ } else {
401
+ // do nothing
402
+ }
403
+ } else {
404
+ visited[neighbor] = node->length + 1;
405
+ }
406
+
407
+ q.push(new_path_node(node, neighbor, node->length + 1, pool));
408
+ }
409
+ }
410
+
411
+ // release path nodes
412
+ for (auto node : pool) {
413
+ delete node;
414
+ }
415
+ return result;
416
+ }
417
+
418
+ private:
419
+ struct path_node_t {
420
+ path_node_t* father;
421
+ string word;
422
+ int length; // 路径长度
423
+
424
+ path_node_t(path_node_t* father_, const string& word_, int length_) :
425
+ father(father_), word(word_), length(length_) {}
426
+ };
427
+
428
+ path_node_t* new_path_node(path_node_t* parent, const string& value,
429
+ int length, vector<path_node_t*>& pool) {
430
+ path_node_t* node = new path_node_t(parent, value, length);
431
+ pool.push_back(node);
432
+
433
+ return node;
434
+ }
435
+ vector<string> gen_path(const path_node_t* node) {
436
+ vector<string> path;
437
+
438
+ while(node != nullptr) {
439
+ path.push_back(node->word);
440
+ node = node->father;
441
+ }
442
+
443
+ reverse(path.begin(), path.end());
444
+ return path;
445
+ }
446
+
447
+ unordered_map<string, unordered_set<string> > build_graph(
448
+ const unordered_set<string>& dict) {
449
+ unordered_map<string, unordered_set<string> > adjacency_list;
450
+
451
+ for (const auto& word : dict) {
452
+ for (size_t i = 0; i < word.size(); ++i) {
453
+ string new_word(word);
454
+ for (char c = 'a' ; c <= 'z' ; c++) {
455
+ // 防止同字母替换
456
+ if (c == new_word[i]) continue;
457
+
458
+ swap(c, new_word[i]);
459
+
460
+ if ((dict.find(new_word) != dict.end())) {
461
+ auto iter = adjacency_list.find(word);
462
+ if (iter != adjacency_list.end()) {
463
+ iter->second.insert(new_word);
464
+ }
465
+ else {
466
+ adjacency_list.insert(pair<string,
467
+ unordered_set<string>>(word, unordered_set<string>()));
468
+ adjacency_list[word].insert(new_word);
469
+ }
470
+ }
471
+ swap(c, new_word[i]); // 恢复该单词
472
+ }
473
+ }
474
+ }
475
+ return adjacency_list;
476
+ }
477
+ };
478
+ \end {Code }
479
+
480
+
330
481
\subsubsection {相关题目 }
331
482
332
483
\begindot
@@ -575,7 +726,18 @@ \subsubsection{如何表示状态}
575
726
vector<vector<state_t> > &result) {
576
727
path.push_back(state);
577
728
if (state == start) {
578
- result.push_back(path);
729
+ if (!result.empty()) {
730
+ if (path.size() < result[0].size()) {
731
+ result.clear();
732
+ result.push_back(path);
733
+ } else if(path.size() == result[0].size()) {
734
+ result.push_back(path);
735
+ } else {
736
+ // throw exception
737
+ }
738
+ } else {
739
+ result.push_back(path);
740
+ }
579
741
reverse(result.back().begin(), result.back().end());
580
742
} else {
581
743
for (const auto& f : father[state]) {
@@ -753,6 +915,7 @@ \subsubsection{求所有路径}
753
915
unordered_set<state_t> visited; // 判重
754
916
unordered_map<state_t, vector<state_t> > father; // DAG
755
917
918
+ int level = 0; // 层次
756
919
757
920
// 判断状态是否合法
758
921
auto state_is_valid = [&](const state_t &s) { /*...*/ };
@@ -776,6 +939,11 @@ \subsubsection{求所有路径}
776
939
vector<vector<string> > result;
777
940
current.insert(start);
778
941
while (!current.empty()) {
942
+ ++ level;
943
+ // 如果当前路径长度已经超过当前最短路径长度,可以中止对该路径的
944
+ // 处理,因为我们要找的是最短路径
945
+ if (!result.empty() && level > result[0].size()) break;
946
+
779
947
// 1. 延迟加入visited, 这样才能允许两个父节点指向同一个子节点
780
948
// 2. 一股脑current 全部加入visited, 是防止本层前一个节点扩展
781
949
// 节点时,指向了本层后面尚未处理的节点,这条路径必然不是最短的
@@ -802,3 +970,4 @@ \subsubsection{求所有路径}
802
970
return result;
803
971
}
804
972
\end {Codex }
973
+
0 commit comments