题目:

Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from start to end, such that:

  1. Only one letter can be changed at a time
  2. 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.

代码:

class Solution {
public:
vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) {
vector<vector<string> > ret;
queue<pair<string, vector<string>>> que;
set<string> used;
vector<string> path;
vector<string> endOfLevel;
path.push_back(start);
bool ifFind = false;
que.push(make_pair(start, path));
que.push(make_pair("", endOfLevel));
while ( !que.empty() )
{
string curr = que.front().first;
vector<string> path = que.front().second;
que.pop();
if (curr!="")
{
for ( size_t i = ; i < curr.size(); ++i )
{
char curr_c = curr[i];
for ( char c='a'; c <= 'z'; ++c )
{
if ( c==curr_c ) continue;
curr[i] = c;
if ( curr==end )
{
ifFind = true;
vector<string> tmp;
tmp.push_back(curr);
tmp.insert(tmp.begin(), path.begin(),path.end());
ret.push_back(tmp);
}
if ( dict.find(curr)!=dict.end() )
{
vector<string> tmp;
tmp.push_back(curr);
tmp.insert(tmp.begin(), path.begin(), path.end());
que.push(make_pair(curr, tmp));
used.insert(curr);
}
}
curr[i] = curr_c;
}
}
else if ( !que.empty() )
{
if ( !ifFind )
{
for ( set<string>::iterator i = used.begin(); i!=used.end(); ++i )
{
dict.erase(*i);
}
used.clear();
que.push(make_pair("", endOfLevel));
}
else
{
break;
}
}
}
return ret;
}
};

tips:

主要思路是对Word Ladder这道题(http://www.cnblogs.com/xbf9xbf/p/4527302.html)扩展。

麻烦之处在于要找到所有的最短路径,关键在于BFS方法搜寻到最短路径后,如何回溯发现路径上所有的前驱节点。

1. 队列中存放每个word及其前驱节点(存成一个vector)

2. BFS的同一层中,dict中的word可以复用;但dict的一个单词不能再不同层中都使用,否则就死循环了;记录每层用到过哪些word, 这层结束后从dict中删除

3. 所有最短的路径一定存在于BFS的同一层;因此设置一个标记变量ifFind,用于标记是否在某一层找到了匹配的路径,如果找到就不往下找了

没想到第一次提交就AC了,就是代码效率太低了。

上述代码相对来说比较简洁,但是queue中要维护每个节点的所有前驱路径vector<string>,整个路径跟着出队入队太太耗时了。

网上有一种维护每个节点前驱节点的hashmap,前驱路径不用入栈,可能会节省不少时间,再研究一下这个实现思路。

==========================================

实现了利用hashmap保存每条路径上前驱节点的算法,代码如下:

class Solution {
public:
vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) {
vector<vector<string> > ret;
// record each word's pre words
map<string, vector<string> > wordPre;
vector<string> pre;
for ( unordered_set<string>::iterator i = dict.begin(); i!=dict.end(); ++i ) { wordPre[*i] = pre; }
// queue for bfs & "" denotes the end of a certain level
queue<string> que;
que.push(start);
que.push("");
// used records the dict words which used in the current level when bfs
set<string> used;
bool ifFind = false;
// bfs all shortest available paths from start to end
while ( !que.empty() )
{
string ori = que.front();
string curr = ori;
que.pop();
if (curr!="")
{
for ( size_t i = ; i < curr.size(); ++i )
{
char curr_c = curr[i];
for ( char c='a'; c <= 'z'; ++c )
{
if ( c==curr_c ) continue;
curr[i] = c;
if ( curr==end )
{
ifFind = true;
wordPre[end].push_back(ori);
continue;
}
if ( dict.find(curr)!=dict.end() )
{
wordPre[curr].push_back(ori);
if (used.find(curr)==used.end())
{
que.push(curr);
used.insert(curr);
}
}
}
curr[i] = curr_c;
}
}
else if ( !que.empty() )
{
if ( !ifFind )
{
for ( set<string>::iterator i = used.begin(); i!=used.end(); ++i )
{
dict.erase(*i);
}
used.clear();
que.push("");
}
else { break; }
}
}
// backtracing all shorest paths
if ( wordPre.find(end)!=wordPre.end() )
{
vector<string> tmp;
tmp.push_back(end);
Solution::backTracingPaths(ret, wordPre, start, end, tmp);
}
return ret;
}
static void backTracingPaths(
vector<vector<string> >& ret,
map<string, vector<string> >& wordPre,
string start,
string curr,
vector<string>& tmp)
{
if ( curr==start )
{
reverse(tmp.begin(), tmp.end());
ret.push_back(tmp);
reverse(tmp.begin(),tmp.end());
return;
}
vector<string> pre = wordPre[curr];
for (size_t i = ; i < pre.size(); ++i )
{
tmp.push_back(pre[i]);
Solution::backTracingPaths(ret, wordPre, start, pre[i], tmp);
tmp.pop_back();
}
}
};

tips:

1. 利用hashap保存bfs过程中dict中每个word的前驱节点;这样queue中只需要保存bfs的每一层访问的节点即可,省去了vector入队出队的大量耗时。

2. 再获得所有最短路径之后,可以从hashmap中的end节点出发往前回溯;回溯的思路是dfs,终止条件是前驱节点是start,就发现了一条完整路径(这里有个地方需要注意,从end出发dfs的方法得到的路径是倒着的,因此再加入ret时需要reverse一次;同时为了不影响其余的回溯,不能改变tmp本身,需要复制到result里面再reverse)

3. 这里有一个细节需要注意:在queue.push(curr)的时候,需要注意去重,不要把当前层的节点重复加入到队列中。第一次没有注意这个问题,一直报超时。在自己第一个解法中,queue中保存的是节点和前面所有的路径,即使curr相同,但是其之前的path一定是不同的,所以都加入queue中也无妨。这种方法其实就把第二种方法的dfs过程省了,但是中间vector<string>耗费了大量的入队出队时间。

之前学习的时候,实现BFS的过程有一种双队列的方法,换这种方式实现这道题一下。

======================================================

改用双队列实现BFS的方法又完成一次AC,代码如下:

class Solution {
public:
vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) {
vector<vector<string> > ret;
// BFS product by two queues
queue<string> curr;
curr.push(start);
queue<string> next;
// word and its previous word in potential paths
map<string, vector<string>> wordPre;
vector<string> pre;
for ( unordered_set<string>::iterator i = dict.begin(); i!=dict.end(); ++i )
{
wordPre[*i] = pre;
}
// if find path
bool ifFind = false;
// already used
set<string> used;
// BFS progress
while ( !curr.empty() )
{
while ( !curr.empty() )
{
string word = curr.front();
string tmp = word;
curr.pop();
for ( size_t i = ; i < tmp.size(); ++i )
{
char ori = tmp[i];
for ( char c = 'a'; c <= 'z'; ++c )
{
if ( c==tmp[i] ) continue;
tmp[i] = c;
if ( tmp==end )
{
ifFind = true;
wordPre[end].push_back(word);
continue;
}
if ( dict.find(tmp)!=dict.end() )
{
wordPre[tmp].push_back(word);
if ( used.find(tmp)==used.end())
{
next.push(tmp);
used.insert(tmp);
}
}
}
tmp[i] = ori;
}
}
if ( !ifFind )
{
std::swap(next, curr);
for ( set<string>::iterator i = used.begin(); i!=used.end(); ++i )
{
dict.erase(*i);
}
used.clear();
}
}
// backtracing all shorest paths
if ( wordPre.find(end)!=wordPre.end() )
{
vector<string> tmp;
tmp.push_back(end);
Solution::dfs(ret, tmp, start, end, wordPre);
}
return ret;
}
static void dfs(
vector<vector<string> >& ret,
vector<string>& tmp,
string start,
string curr,
map<string, vector<string>>& wordPre)
{
if ( curr==start )
{
std::reverse(tmp.begin(), tmp.end());
ret.push_back(tmp);
std::reverse(tmp.begin(), tmp.end());
return;
}
vector<string> pre = wordPre[curr];
for ( size_t i = ; i < pre.size(); ++i )
{
tmp.push_back(pre[i]);
Solution::dfs(ret, tmp, start, pre[i], wordPre);
tmp.pop_back();
}
}
};

tips:

双队列的方式实现BFS主要的好处是省去了判断每一层结束的代码,且只需要swap(next,curr)即可(交换指针操作O(1))

代码整洁的同时,并没有影响时间复杂度和空间复杂度,应该说是优于第二种实现的。

重写这部分代码的时候,突然对如下的代码有些疑问:

                            if ( dict.find(tmp)!=dict.end() )
{
wordPre[tmp].push_back(word);
if ( used.find(tmp)==used.end())
{
next.push(tmp);
used.insert(tmp);
}
}

为什么每次wordPre[tmp].push_back(word)就不用检查是否重复?而加入next队列的时候就需要检查重复呢?

这是一个思维误区,因为这两个说的不是一个事情。

1. word是每次从队列头部弹出来的,加入队列前要查重的,因此不可能有两个相同的word;而tmp,是由word的某个位置变化一个字母得来的。

比如,队列curr中有{"hot" "cot" },而字典dict中有{“got”}。

  当word为“hot”时,tmp会取到“got” → wordPre["got"].push_back("hot")

当word为“cot”时,tmp也会取到“got” → wordPre["got"].push_back("cot")

  到此为止,可以看到由于队列中word不会重复,wordPre[tmp].push_back(word)是没问题的。

2. 再看next.push(tmp):used的作用是记录在BFS某一层的过程中,dict中出现过的单词。显然,在上例中"got"这个单词作出现了两次,都作为tmp。

如果不加区分,把两个got都加入了next队列中,那么再最后dfs回溯的过程中必然会输出重复的路径,并且如果这种got出现了很多次,会大大影响迭代效率,因此需要无论从结果正确性还是代码效率都要判断,不能让队列中有重复的元素。

总结起来,就是dict中的一个单词,可以有多个不同的前驱;但是每个单词不能再同一层队列中出现多次,更不能在BFS的不同层中出现。

存几个参考过的blog

http://www.cnblogs.com/TenosDoIt/p/3443512.html

主要参考上面的思路,做了一些细节的处理,还是要感谢作者share solution。

完毕。

====================================================

第二次过这道题,还是很复杂,很多细节要注意。

class Solution {
public:
vector<vector<string> > findLadders(
string start, string end, unordered_set<string> &dict)
{
vector<vector<string> > ret;
map<string, vector<string> > preWords;
bool ladderFind = false;
queue<string> curr;
queue<string> next;
curr.push(start);
set<string> used; // record words in dict that used in this level
while ( !curr.empty() )
{
while ( !curr.empty() )
{
string word = curr.front();
string pre = word; // remain the original word as potential pre word
curr.pop();
for ( int i=; i<word.size(); ++i )
{
// change word i's char to match words in dict
char ori = word[i];
for ( char c='a'; c<='z'; ++c )
{
if (word[i]==c) continue;
word[i] = c;
if ( word==end ) // move to the end word
{
ladderFind = true;
preWords[end].push_back(pre);
break;
}
if ( dict.find(word)!=dict.end() ) // find a following word from pre word
{
used.insert(word);
preWords[word].push_back(pre);
continue;
}
}
word[i] = ori;
}
}
if ( ladderFind ) break;
// erase all the words in dict used in this level
for ( set<string>::iterator i=used.begin(); i!=used.end(); ++i )
{
next.push(*i);
dict.erase(*i);
}
swap(curr, next);
used.clear();
}
if (ladderFind)
{
vector<string> tmp;
tmp.push_back(end);
Solution::dfs(ret, start, end, tmp, preWords);
}
return ret;
}
static void dfs(
vector<vector<string> >& ret,
string start,
string curr,
vector<string> tmp,
map<string, vector<string> >& preWords
)
{
if ( curr==start )
{
reverse(tmp.begin(), tmp.end());
ret.push_back(tmp);
reverse(tmp.begin(), tmp.end());
return;
}
for ( int i=; i<preWords[curr].size(); ++i )
{
tmp.push_back(preWords[curr][i]);
Solution::dfs(ret, start, preWords[curr][i], tmp, preWords);
tmp.pop_back();
}
}
};

【Word Ladder II】cpp的更多相关文章

  1. 【Word Break II】cpp

    题目: Given a string s and a dictionary of words dict, add spaces in s to construct a sentence where e ...

  2. 【Unique Paths II】cpp

    题目: Follow up for "Unique Paths": Now consider if some obstacles are added to the grids. H ...

  3. 【Path Sum II】cpp

    题目: Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals the give ...

  4. 【Spiral Matrix II】cpp

    题目: Given an integer n, generate a square matrix filled with elements from 1 to n2 in spiral order. ...

  5. 【palindrome partitioning II】cpp

    题目: Given a string s, partition s such that every substring of the partition is a palindrome. Return ...

  6. 【Jump Game II 】cpp

    题目: Given an array of non-negative integers, you are initially positioned at the first index of the ...

  7. 【Combination Sum II 】cpp

    题目: Given a collection of candidate numbers (C) and a target number (T), find all unique combination ...

  8. 【Single Num II】cpp

    题目: Given an array of integers, every element appears three times except for one. Find that single o ...

  9. 【leetcode】Word Ladder II

      Word Ladder II Given two words (start and end), and a dictionary, find all shortest transformation ...

随机推荐

  1. python super用法

    普通继承 class FooParent(object): def __init__(self): self.parent = 'I\'m the parent.' print 'Parent' de ...

  2. python3应用例子01(进度条)

    #!/usr/bin/env python# -*- coding:utf-8 -*- import sysimport time def bar(num, total): rate = num / ...

  3. Android从本地选择图片文件转为Bitmap,并用zxing解析Bitmap

    如何从本地选择图片文件 使用Intent调用系统相册后,onActivityResult函数返回的是Uri格式的路径 /** * 打开系统相册 */ private void openSysAlbum ...

  4. cin对象的一些常用方法使用总结

    >> 最初定义的是右移,当但是出现在 cin >>中的时候这个符号被重载了,变成了一个流操作,在用户通过键盘输入信息的时候,所有内容都会先直接存储在一个叫输入缓冲区的的地方,c ...

  5. 魅族MX3 Flyme3.0找回手机功能支持远程拍照密码错两次自动拍照

    进入Flyme页面(http://app.meizu.com/),选择“查找手机”即可进行查找自己登记的魅族系列手机. 如果您在一个账号下登记过N多魅族系列手机,那么都是可以进行查找的,参见下图 魅族 ...

  6. 关于HTML(含HTML5)的块级元素和行级(内联)元素总结

    1.首先我们要知道什么是块级元素和行级(内联)元素? 块级(block)元素的特点: ①总是在新行上开始: ②高度,行高以及外边距和内边距都可控制: ③宽度缺省是它的容器的100%,除非设定一个宽度: ...

  7. JS位运算和遍历

    JS位运算符 整数 有符号整数:允许使用正数和负数,第32位作为符号位,前31位才是存储位 无符号整数:只允许用正数 如果用n代表位 位数 = 2^n-1 由于位数(1.2.4.8.16...)中只有 ...

  8. cf540D. Bad Luck Island(概率dp)

    题意 岛上有三个物种:剪刀$s$.石头$r$.布$p$ 其中剪刀能干掉布,布能干掉石头,石头能干掉剪刀 每天会从这三个物种中发生一场战争(也就是说其中的一个会被干掉) 问最后仅有$s/r/p$物种生存 ...

  9. 【dp】奶牛家谱 Cow Pedigrees

    令人窒息的奶牛题 题目描述 农民约翰准备购买一群新奶牛. 在这个新的奶牛群中, 每一个母亲奶牛都生两个小奶牛.这些奶牛间的关系可以用二叉树来表示.这些二叉树总共有N个节点(3 <= N < ...

  10. 【Umezawa's Jitte】真正用起来svn来管理版本

    之前用过一次 但是没有真正的用起来 只是知道了一些基本概念 好了 决定开始真正的用这个svn了 参考大神http://www.cnblogs.com/wrmfw/archive/2011/09/08/ ...