单词接龙
2015-09-15 11:07
344 查看
题目一:给出两个单词(start和end)和一个字典,找到从start到end的最短转换序列
每次只能改变一个字母。
变换过程中的中间单词必须在字典中出现。
例子:
给出数据如下:
start = “hit”
end = “cog”
dict = [“hot”,”dot”,”dog”,”lot”,”log”]
一个最短的变换序列是 “hit” -> “hot” -> “dot” -> “dog” -> “cog”,
返回它的长度 5。
方法:求最短路径、树最小深度问题bfs最适合。本题bfs要注意的问题:
和当前单词相邻的单词是:对当前单词改变一个字母且在字典中存在的单词。
找到一个单词的相邻单词,加入bfs队列后,要从字典中删除,因为不删除的话会造成类似于hog->hot->hog的死循环。而删除对求最短路径没有影响,因为我们第一次找到该单词肯定是最短路径,即使后面其他单词也可能转化得到它,路径肯定不会比当前的路径短(如果要输出所有最短路径,则不能立即从字典中删除,具体见下一题)。
bfs队列中用NULL来标识层与层的间隔,每次碰到层的结尾,遍历深度+1。
题目二:给出两个单词(start和end)和一个字典,找出所有从start到end的最短转换序列。所有单词具有相同的长度。所有单词都只包含小写字母。
例子:
给出数据如下:
start = “hit”
end = “cog”
dict = [“hot”,”dot”,”dog”,”lot”,”log”]
返回
[
[“hit”,”hot”,”dot”,”dog”,”cog”],
[“hit”,”hot”,”lot”,”log”,”cog”]
]
方法:本题主要的框架和上一题是一样,但是还要解决两个额外的问题:一、 怎样保证求得所有的最短路径;二、 怎样构造这些路径
第一问题:
不能像上一题第二点注意那样,找到一个单词相邻的单词后就立马把它从字典里删除,因为当前层还有其他单词可能和该单词是相邻的,这也是一条最短路径,比如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)
每次只能改变一个字母。
变换过程中的中间单词必须在字典中出现。
例子:
给出数据如下:
start = “hit”
end = “cog”
dict = [“hot”,”dot”,”dog”,”lot”,”log”]
一个最短的变换序列是 “hit” -> “hot” -> “dot” -> “dog” -> “cog”,
返回它的长度 5。
方法:求最短路径、树最小深度问题bfs最适合。本题bfs要注意的问题:
和当前单词相邻的单词是:对当前单词改变一个字母且在字典中存在的单词。
找到一个单词的相邻单词,加入bfs队列后,要从字典中删除,因为不删除的话会造成类似于hog->hot->hog的死循环。而删除对求最短路径没有影响,因为我们第一次找到该单词肯定是最短路径,即使后面其他单词也可能转化得到它,路径肯定不会比当前的路径短(如果要输出所有最短路径,则不能立即从字典中删除,具体见下一题)。
bfs队列中用NULL来标识层与层的间隔,每次碰到层的结尾,遍历深度+1。
class Solution { public: int ladderLength(string start, string end, unordered_set<string> &dict) { // IMPORTANT: Please reset any member data you declared, as // the same Solution instance will be reused for each test case. //BFS遍历找到的第一个匹配就是最短转换,空字符串是层与层之间的分隔标志 queue<string> Q; Q.push(start); Q.push(""); int res = 1; while(Q.empty() == false) { string str = Q.front(); Q.pop(); if(str != "") { int strLen = str.length(); for(int i = 0; i < strLen; i++) { char tmp = str[i]; for(char c = 'a'; c <= 'z'; c++) { if(c == tmp)continue; str[i] = c; if(str == end)return res+1; if(dict.find(str) != dict.end()) { Q.push(str); dict.erase(str); } } str[i] = tmp; } } else if(Q.empty() == false) {//到达当前层的结尾,并且不是最后一层的结尾 res++; Q.push(""); } } return 0; } };
题目二:给出两个单词(start和end)和一个字典,找出所有从start到end的最短转换序列。所有单词具有相同的长度。所有单词都只包含小写字母。
例子:
给出数据如下:
start = “hit”
end = “cog”
dict = [“hot”,”dot”,”dog”,”lot”,”log”]
返回
[
[“hit”,”hot”,”dot”,”dog”,”cog”],
[“hit”,”hot”,”lot”,”log”,”cog”]
]
方法:本题主要的框架和上一题是一样,但是还要解决两个额外的问题:一、 怎样保证求得所有的最短路径;二、 怎样构造这些路径
第一问题:
不能像上一题第二点注意那样,找到一个单词相邻的单词后就立马把它从字典里删除,因为当前层还有其他单词可能和该单词是相邻的,这也是一条最短路径,比如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)
class Solution { public: typedef unordered_set<string>::iterator HashIter; vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) { // Note: The Solution object is instantiated only once and is reused by each test case. queue<string> Q; Q.push(start); Q.push(""); bool hasFound = false; unordered_map<string,vector<string> >prePath;//前驱路径 unordered_set<string> hashtable;//保证bfs时插入队列的元素不存在重复 while(Q.empty() == false) { string str = Q.front(), strCopy = str; Q.pop(); if(str != "") { int strLen = str.length(); for(int i = 0; i < strLen; i++) { char tmp = str[i]; for(char c = 'a'; c <= 'z'; c++) { if(c == tmp)continue; str[i] = c; if(str == end) { hasFound = true; prePath[end].push_back(strCopy); //找到了一条最短路径,当前单词的其它转换就没必要 goto END; } if(dict.find(str) != dict.end()) { prePath[str].push_back(strCopy); //保证bfs时插入队列的元素不存在重复 if(hashtable.find(str) == hashtable.end()) {Q.push(str); hashtable.insert(str);} } } str[i] = tmp; } } else if(Q.empty() == false)//到当前层的结尾,且不是最后一层的结尾 { if(hasFound)break; //避免进入死循环,把bfs上一层插入队列的元素从字典中删除 for(HashIter ite = hashtable.begin(); ite != hashtable.end(); ite++) dict.erase(*ite); hashtable.clear(); Q.push(""); } END: ; } vector<vector<string> > res; if(prePath.find(end) == prePath.end())return res; vector<string> tmpres; tmpres.push_back(end); ConstructResult(prePath, res, tmpres, start, end); return res; } private: //从前驱路径中回溯构造path void ConstructResult(unordered_map<string,vector<string> > &prePath, vector<vector<string> > &res, vector<string> &tmpres, string &start, string &end) { if(start == end) { reverse(tmpres.begin(), tmpres.end()); res.push_back(tmpres); reverse(tmpres.begin(), tmpres.end()); return; } vector<string> &pre = prePath[end]; for(int i = 0; i < pre.size(); i++) { tmpres.push_back(pre[i]); ConstructResult(prePath, res, tmpres, start, pre[i]); tmpres.pop_back(); } } };
相关文章推荐
- 详解图的应用(最小生成树、拓扑排序、关键路径、最短路径)
- python编写的最短路径算法
- 【算法】最短路径之A*搜索
- Dijkstra和floyd——求单源点最短路径
- 初学图论-Dijkstra单源最短路径算法基于优先级队列(Priority Queue)的实现
- Surrounded Regions
- Word Ladder, Gray Code
- UVA 11624
- HDU1495
- HDU2612 Find a way
- HDU1241 Oil Deposits
- poj 1511 Invitation Cards
- Hdu2444二分图
- 最少步数BFS
- 转v_JULY_v的 BFS和DFS优先搜索算法
- 2015 寒假搜索专题 I - Meteor Shower(BFS)
- 想讨论一个指定必经节点求最短路径思路,大伙有什么思路没有?
- Same Tree
- E - Roads in the North
- Dijkstra算法