Skip to content

Commit d6e3b41

Browse files
committed
Added an solution for Word Ladder II
1 parent 242cf9d commit d6e3b41

File tree

2 files changed

+175
-6
lines changed

2 files changed

+175
-6
lines changed

C++/chapBFS.tex

Lines changed: 175 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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+

C++/leetcode-cpp.pdf

4.63 KB
Binary file not shown.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy