@@ -87,6 +87,7 @@ \subsubsection{单队列}
8787 for (size_t i = 0; i < s.word.size(); ++i) {
8888 state_t new_state(s.word, s.step + 1);
8989 for (char c = 'a' ; c <= 'z' ; c++) {
90+ // 防止同字母替换
9091 if (c == new_state.word[i]) continue;
9192
9293 swap(c, new_state.word[i]);
@@ -150,6 +151,7 @@ \subsubsection{双队列}
150151 for (size_t i = 0; i < s.size(); ++i) {
151152 string new_word(s);
152153 for (char c = 'a' ; c <= 'z' ; c++) {
154+ // 防止同字母替换
153155 if (c == new_word[i]) continue;
154156
155157 swap(c, new_word[i]);
@@ -235,12 +237,11 @@ \subsubsection{描述}
235237\subsubsection {分析 }
236238跟 Word Ladder比,这题是求路径本身,不是路径长度,也是BFS,略微麻烦点。
237239
238- 这题跟普通的广搜有很大的不同,就是要输出所有路径,因此在记录前驱和判重地方与普通广搜略有不同 。
240+ 求一条路径和求所有路径有很大的不同,求一条路径,每个状态节点只需要记录一个前驱即可;求所有路径时,有的状态节点可能有多个父节点,即要记录多个前驱 。
239241
242+ 如果当前路径长度已经超过当前最短路径长度,可以中止对该路径的处理,因为我们要找的是最短路径。
240243
241- \subsubsection {单队列 }
242-
243- 单队列无法求解所有路径。因为 需要一个 \fn {queue} 和 \fn {unordered_set} 的综合体,这是不可能的。
244+ 单队列无法求解所有路径。因为 需要一个 \fn {queue} 和 \fn {unordered_set} 的综合体,这是不可能的。本题可以用双队列来求解。
244245
245246
246247\subsubsection {双队列 }
@@ -259,13 +260,16 @@ \subsubsection{双队列}
259260 unordered_set<string> visited; // 判重
260261 unordered_map<string, vector<string> > father; // DAG
261262
263+ int level = 0; // 层次
264+
262265 auto state_is_target = [&](const string &s) {return s == end;};
263266 auto state_extend = [&](const string &s) {
264267 unordered_set<string> result;
265268
266269 for (size_t i = 0; i < s.size(); ++i) {
267270 string new_word(s);
268271 for (char c = 'a' ; c <= 'z' ; c++) {
272+ // 防止同字母替换
269273 if (c == new_word[i]) continue;
270274
271275 swap(c, new_word[i]);
@@ -284,6 +288,11 @@ \subsubsection{双队列}
284288 vector<vector<string> > result;
285289 current.insert(start);
286290 while (!current.empty()) {
291+ ++ level;
292+ // 如果当前路径长度已经超过当前最短路径长度,可以中止对该路径的
293+ // 处理,因为我们要找的是最短路径
294+ if (!result.empty() && level > result[0].size()) break;
295+
287296 // 1. 延迟加入visited, 这样才能允许两个父节点指向同一个子节点
288297 // 2. 一股脑current 全部加入visited, 是防止本层前一个节点扩展
289298 // 节点时,指向了本层后面尚未处理的节点,这条路径必然不是最短的
@@ -314,7 +323,18 @@ \subsubsection{双队列}
314323 vector<vector<string> > &result) {
315324 path.push_back(word);
316325 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+ }
318338 reverse(result.back().begin(), result.back().end());
319339 } else {
320340 for (const auto& f : father[word]) {
@@ -327,6 +347,137 @@ \subsubsection{双队列}
327347\end {Code }
328348
329349
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+
330481\subsubsection {相关题目 }
331482
332483\begindot
@@ -575,7 +726,18 @@ \subsubsection{如何表示状态}
575726 vector<vector<state_t> > &result) {
576727 path.push_back(state);
577728 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+ }
579741 reverse(result.back().begin(), result.back().end());
580742 } else {
581743 for (const auto& f : father[state]) {
@@ -753,6 +915,7 @@ \subsubsection{求所有路径}
753915 unordered_set<state_t> visited; // 判重
754916 unordered_map<state_t, vector<state_t> > father; // DAG
755917
918+ int level = 0; // 层次
756919
757920 // 判断状态是否合法
758921 auto state_is_valid = [&](const state_t &s) { /*...*/ };
@@ -776,6 +939,11 @@ \subsubsection{求所有路径}
776939 vector<vector<string> > result;
777940 current.insert(start);
778941 while (!current.empty()) {
942+ ++ level;
943+ // 如果当前路径长度已经超过当前最短路径长度,可以中止对该路径的
944+ // 处理,因为我们要找的是最短路径
945+ if (!result.empty() && level > result[0].size()) break;
946+
779947 // 1. 延迟加入visited, 这样才能允许两个父节点指向同一个子节点
780948 // 2. 一股脑current 全部加入visited, 是防止本层前一个节点扩展
781949 // 节点时,指向了本层后面尚未处理的节点,这条路径必然不是最短的
@@ -802,3 +970,4 @@ \subsubsection{求所有路径}
802970 return result;
803971}
804972\end {Codex }
973+
0 commit comments