[LeetCode] Word Ladder II
Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from start to end, such that:
- Only one letter can be changed at a time
- Each intermediate word must exist in the dictionary
For example,
Given:
start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]
Return
[ ["hit","hot","dot","dog","cog"], ["hit","hot","lot","log","cog"] ]
Note:
- All words have the same length.
- All words contain only lowercase alphabetic characters.
思路转载 http://www.cnblogs.com/TenosDoIt/p/3443512.html
分析:本题主要的框架和上一题是一样,但是还要解决两个额外的问题:一、 怎样保证求得所有的最短路径;二、 怎样构造这些路径
第一问题:
- 不能像上一题第二点注意那样,找到一个单词相邻的单词后就立马把它从字典里删除,因为当前层还有其他单词可能和该单词是相邻的,这也是一条最短路径,比如hot->hog->dog->dig和hot->dot->dog->dig,找到hog的相邻dog后不能立马删除,因为和hog同一层的单词dot的相邻也是dog,两者均是一条最短路径。但是为了避免进入死循环,再hog、dot这一层的单词便利完成后dog还是得从字典中删除。即等到当前层所有单词遍历完后,和他们相邻且在字典中的单词要从字典中删除。
- 如果像上面那样没有立马删除相邻单词,就有可能把同一个单词加入bfs队列中,这样就会有很多的重复计算(比如上面例子提到的dog就会被2次加入队列)。因此我们用一个哈希表来保证加入队列中的单词不会重复,哈希表在每一层遍历完清空(代码中hashtable)。
- 当某一层的某个单词转换可以得到end单词时,表示已经找到一条最短路径,那么该单词的其他转换就可以跳过。并且遍历完这一层以后就可以跳出循环,因为再往下遍历,肯定会超过最短路径长度
第二个问题:
- 为了输出最短路径,我们就要在比bfs的过程中保存好前驱节点,比如单词hog通过一次变换可以得到hot,那么hot的前驱节点就包含hog,每个单词的前驱节点有可能不止一个,那么每个单词就需要一个数组来保存前驱节点。为了快速查找因此我们使用哈希表来保存所有单词的前驱路径,哈希表的key是单词,value是单词数组。(代码中的unordered_map<string,vector<string> >prePath)
- 有了上面的前驱路径,可以从目标单词开始递归的构造所有最短路径(代码中的函数 ConstructResult)
1 class Solution { 2 public: 3 vector<vector<string> > findLadders(string start, string end, unordered_set<string>& dict) 4 { 5 vector<vector<string> > result; 6 7 if(start.empty() || end.empty() ) 8 return result; 9 if(start.size() != end.size()) 10 return result; 11 12 size_t size = start.size(); 13 14 unordered_set<string> cur, next;//use set to aviod add duplicate element 15 unordered_map<string, vector<string> > father; 16 unordered_set<string> visited; // 判重 17 18 cur.insert(start); 19 20 bool found = false; 21 22 while(!cur.empty() && !found) 23 { 24 for (const auto& word : cur) 25 visited.insert(word); 26 for(unordered_set<string>::iterator it = cur.begin(); it != cur.end(); it++) 27 { 28 string curStr= *it; 29 30 //cout << "=========" <<curStr <<"========================" <<endl; 31 for(int i = 0; i< size; i++) 32 { 33 for(char j = 'a'; j <= 'z'; j++ ) 34 { 35 string nextStr = curStr; 36 if(nextStr[i] == j) 37 continue; 38 nextStr[i] = j; 39 if(nextStr.compare(end) == 0) 40 { 41 //if found, just traverse this layer, not go to next layer 42 found = true; 43 father[nextStr].push_back(curStr); 44 } 45 46 #if 0 47 cout << "nextStr = " << nextStr<< endl; 48 cout << "nextStr [i] = " << nextStr[i]<< endl; 49 cout << "i = " << i<< endl; 50 cout << "j = " << j<< endl; 51 #endif 52 //if(dict.find(nextStr) != dict.end() && cur.find(nextStr) == cur.end()) 53 if(dict.find(nextStr) != dict.end() && !visited.count(new_word)) 54 // must add "&& cur.find(nextStr) == cur.end()" 55 // to avoid the same level 's elemnt point each other 56 // for example hot --> dot 57 // hot --> lot 58 // but when you travel to dot, may happen dot --> lot 59 // but when you travel to lot, may happen lot --> dot 60 // so when add it to next, we must confirm that lot is not in this level 61 { 62 //cout << "insert\t" << nextStr<< "\tto next queue" << endl; 63 next.insert(nextStr); 64 father[nextStr].push_back(curStr); 65 } 66 } 67 } 68 } 69 70 // remove the cur layer's elements form dict 71 for(unordered_set<string>::iterator it = cur.begin(); it != cur.end(); it++) 72 { 73 string tmp = *it; 74 if(dict.find(tmp) != dict.end()) 75 { 76 dict.erase(dict.find(tmp)); 77 //cout << "erase \t" << tmp <<endl; 78 } 79 } 80 81 cur.clear(); 82 swap(cur, next); 83 } 84 85 vector<string> path; 86 for(unordered_map<string, vector<string> >::iterator it = father.begin(); it != father.end(); it++) 87 { 88 string str =(*it).first; 89 vector<string> vect =(*it).second; 90 //cout << "map key :" << str <<endl; 91 for(int i = 0; i < vect.size(); i++) 92 { 93 //cout << "\tmap value:" << vect[i]<<endl; 94 } 95 } 96 genPath(start, end, path, father, result); 97 return result; 98 } 99 100 void genPath(string start, string end, vector<string>& path, unordered_map<string, vector<string> >map, vector<vector<string> >& result) 101 { 102 path.push_back(end); 103 if(start == end) 104 { 105 reverse(path.begin(), path.end()); 106 result.push_back(path); 107 //printVector(path); 108 reverse(path.begin(), path.end()); 109 } 110 else 111 { 112 int size = map[end].size(); 113 for( int i = 0; i < size; i++) 114 { 115 string str = map[end][i]; 116 genPath(start, str, path, map, result); 117 } 118 } 119 path.pop_back(); 120 } 121 };
大数据会超时,
转一个网上找的能通过的版本,随后在仔细分析差距在哪里吧。。
1 class Solution { 2 public: 3 vector<vector<string> > findLadders(string start, string end, 4 const unordered_set<string> &dict) { 5 unordered_set<string> current, next; // 当前层,下一层,用集合是为了去重 6 unordered_set<string> visited; // 判重 7 unordered_map<string, vector<string> > father; // 树 8 bool found = false; 9 auto state_is_target = [&](const string &s) {return s == end;}; 10 auto state_extend = [&](const string &s) { 11 unordered_set<string> result; 12 for (size_t i = 0; i < s.size(); ++i) { 13 string new_word(s); 14 for (char c = 'a'; c <= 'z'; c++) { 15 if (c == new_word[i]) continue; 16 swap(c, new_word[i]); 17 if ((dict.count(new_word) > 0|| new_word == end) && 18 !visited.count(new_word)) { 19 result.insert(new_word); 20 } 21 swap(c, new_word[i]); // 恢复该单词 22 } 23 } 24 return result; 25 }; 26 current.insert(start); 27 while (!current.empty() && !found) { 28 // 先将本层全部置为已访问,防止同层之间互相指向 29 for (const auto& word : current) 30 visited.insert(word); 31 for (const auto& word : current) { 32 const auto new_states = state_extend(word); 33 for (const auto &state : new_states) { 34 if (state_is_target(state)) found = true; 35 next.insert(state); 36 father[state].push_back(word); 37 // visited.insert(state); // 移动到最上面了 38 } 39 } 40 current.clear(); 41 swap(current, next); 42 } 43 vector<vector<string> > result; 44 if (found) { 45 vector<string> path; 46 gen_path(father, path, start, end, result); 47 } 48 return result; 49 } 50 private: 51 void gen_path(unordered_map<string, vector<string> > &father, 52 vector<string> &path, const string &start, const string &word, 53 vector<vector<string> > &result) { 54 path.push_back(word); 55 if (word == start) { 56 result.push_back(path); 57 reverse(result.back().begin(), result.back().end()); 58 } else { 59 for (const auto& f : father[word]) { 60 gen_path(father, path, start, f, result); 61 } 62 } 63 path.pop_back(); 64 } 65 };