Given two words (start and end), and a dictionary, find the length of shortest transformation sequence 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"]
As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog"
,
return its length 5
.
Note:
- Return 0 if there is no such transformation sequence.
- All words have the same length.
- All words contain only lowercase alphabetic characters.
最短距离,直接用BFS暴力解决,直接就过了。比较简单,直接看代码吧:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 class Solution { 2 public: 3 int ladderLength(string start, string end, unordered_set<string> &dict) { 4 unordered_map<string, string> from; 5 queue<string> q; 6 q.push(start); 7 while (!q.empty()) { 8 string cur(q.front()); 9 q.pop();; 10 for (int i = 0; i < cur.size(); ++i) { 11 string tmp = cur; 12 for (char c = 'a'; c <= 'z'; ++c) { 13 if (c != tmp[i]) { 14 cur[i] = c; 15 if (dict.count(cur) == 0 && cur != end) 16 continue; 17 if (cur == end) { 18 int count = 2; 19 cur = tmp; 20 while (cur != start) { 21 cur = from[cur]; 22 ++count; 23 } 24 return count; 25 } 26 if (from.count(cur) == 0) { 27 from[cur] = tmp; 28 q.push(cur); 29 } 30 } 31 } 32 cur[i] = tmp[i]; 33 } 34 } 35 36 return 0; 37 } 38 };
760ms过了大数据。
然后就是看重头戏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.
Word Ladder II与I的区别是,一个是只需要求长度,一个是要求出所有的路径。如何保证能找到所有的路径呢?
假设我们知道最短路径是k,那么到达第k步的节点只可能是深度是k-1的节点,虽然这话很废,但很关键,这一点就要求我们要知道层次信息。
于是,我们就用一个hash表要存已经访问过的节点的层次,还可以用这个表要判断节点是否访问过,当出现我们的目的为止。
接着就是回溯了,回溯的根据还是层次,从end一直往回搜,只找层次相邻的,而变化只有一个字母的节点,直到层次为0。
看代码:
1 class Solution { 2 public: 3 // 递归实现的查找路径 4 void findLaddersHelper(string start, string end, unordered_map<string, int>& depths, 5 vector<vector<string> >& ret, vector<string>& item) { 6 if (start == end) { 7 // 把路径翻转一下 8 reverse(item.begin(), item.end()); 9 ret.push_back(item); 10 reverse(item.begin(), item.end()); 11 return; 12 } 13 int curlev = depths[end]; 14 for (int i = 0; i < end.size(); ++i) { 15 string tmp(end); 16 for (char c = 'a'; c <= 'z'; ++c) { 17 if (c == end[i]) 18 continue; 19 tmp[i] = c; 20 if (depths.count(tmp) == 1 && 21 depths[tmp] == curlev - 1) { 22 item.push_back(tmp); 23 findLaddersHelper(start, tmp, depths, ret, item); 24 item.pop_back(); 25 } 26 } 27 } 28 } 29 vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) { 30 queue<string> q; 31 unordered_map<string, int> depths; 32 depths[start] = 0; 33 q.push(start); 34 vector<vector<string> > ret; 35 // 记录一层的长度 36 int lastcount = 1; 37 int level = 0; 38 while (lastcount > 0) { 39 // 记录新增层的长度 40 int curcount = 0; 41 while (lastcount--) { 42 string cur(q.front()); 43 q.pop(); 44 45 for (int i = 0; i < cur.size(); ++i) { 46 string tmp(cur); 47 for (char c = 'a'; c <= 'z'; ++c) { 48 if (c == cur[i]) 49 continue; 50 tmp[i] = c; 51 if (depths.count(tmp) == 0 && (dict.count(tmp) == 1 || tmp == end)) { 52 q.push(tmp); 53 ++curcount; 54 depths[tmp] = level + 1; 55 } 56 } 57 } 58 } 59 if (depths.count(end)) { // 一层结束后,判断是否出现end了 60 vector<string> item(1, end); 61 findLaddersHelper(start, end, depths, ret, item); 62 return ret; 63 } 64 ++level; 65 lastcount = curcount; 66 } 67 return ret; 68 } 69 };
1200ms过的大数据
这应该算是leetcode上少有的耗时如此之长的题了吧。
最近也拜读了下JULY的程序员编程艺术系列,刚好看到一章讲这题,然后说用双向BFS会好不少,其实也是可以想像的,因为广度优先的话层数越深,广度就越大,而目标只有一个,所以双向至少是去除了一半的计算量,参考JULY书中讲的思路,再结合我原有的单向BFS的代码,于是就有了我自己的双向BFS了。
这里主要注意的是两个问题:
1. 查找的终止条件
2. 如何找出路径
怎么解决?看代码吧~
1 class Solution { 2 public: 3 vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) { 4 // 用于存状态的一些变量 5 queue<string> start_q, end_q; 6 unordered_map<string, int> depth_from_start, depth_from_end; 7 unordered_set<string> meet; 8 depth_from_start[start] = 0; 9 depth_from_end[end] = 0; 10 int level_from_start = 0, level_from_end = 0, count_from_start = 1, count_from_end = 1; 11 start_q.push(start); 12 end_q.push(end); 13 14 vector<vector<string> > ret; 15 16 while (count_from_start > 0 && count_from_end > 0) { 17 if (count_from_end < count_from_start) { 18 count_from_end = nextLevel(end_q, count_from_end, level_from_end, end, 19 depth_from_end, depth_from_start, meet, dict); 20 } else { 21 count_from_start = nextLevel(start_q, count_from_start, level_from_start, start, 22 depth_from_start, depth_from_end, meet, dict); 23 } 24 // 相遇即返回结果 25 if (!meet.empty()) { 26 for (auto mid : meet) { 27 vector<string> item(1, mid); 28 ret.push_back(item); 29 } 30 // 找前向的所有路径 31 findLaddersHelper(depth_from_start, ret); 32 // 翻转 33 for (int i = 0; i < ret.size(); ++i) { 34 reverse(ret[i].begin(), ret[i].end()); 35 } 36 // 结合找后向的所有路径 37 findLaddersHelper(depth_from_end, ret); 38 return ret; 39 } 40 } 41 42 return ret; 43 } 44 private: 45 // 对q添加下一层的节点,返回新添加的节点数,并且层数加1 46 int nextLevel(queue<string>& q, int qcount, int& level, string& end, 47 unordered_map<string, int>& depth, unordered_map<string, int>& other_depth, 48 unordered_set<string>& meet, unordered_set<string>& dict) { 49 int count = 0; 50 while (qcount--) { 51 string tmp = q.front(); 52 q.pop(); 53 for (int i = 0; i < tmp.size(); ++i) { 54 char back = tmp[i]; 55 for (char c = 'a'; c <= 'z'; ++c) { 56 if (c == back) 57 continue; 58 tmp[i] = c; 59 if (depth.count(tmp) == 0 && (dict.count(tmp) == 1 || tmp == end)) { 60 q.push(tmp); 61 depth[tmp] = level + 1; 62 ++count; 63 // 判断是否与另一个map相交了 64 if (other_depth.count(tmp)) { 65 meet.insert(tmp); 66 } 67 } 68 } 69 tmp[i] = back; 70 } 71 } 72 ++level; 73 return count; 74 } 75 // 非递归实现的回溯路径,参考JULY的程序员编程艺术 76 void findLaddersHelper(unordered_map<string, int>& depth, vector<vector<string> >& ret) { 77 vector<vector<string> > temp; 78 while (depth[ret.back().back()] != 0) { 79 ret.swap(temp); 80 ret.clear(); 81 for (int i = 0; i < temp.size(); ++i) { 82 string back = temp[i].back(); 83 int curlev = depth[back]; 84 for (int j = 0; j < back.size(); ++j) { 85 char b = back[j]; 86 for (char c = 'a'; c <= 'z'; ++c) { 87 if (c == b) 88 continue; 89 back[j] = c; 90 if (depth.count(back) && depth[back] == curlev - 1) { 91 temp[i].push_back(back); 92 ret.push_back(temp[i]); 93 temp[i].pop_back(); 94 } 95 } 96 back[j] = b; 97 } 98 } 99 } 100 } 101 };
最终是244ms过了,这提升还是很大的!
这样一下子就把BFS、双BFS、层次搜都过了一遍了,挺不错~