题目:

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. Mybatis-Spring整合Spring

    因为 MyBatis 用 SqlSessionFactory 来创建 SqlSession ,SqlSessionFactoryBuilder 创建 SqlSessionFactory ,而在 Myb ...

  2. pecl install msgpack

    Before the beginning: There are two php version, php5.5, php7.1. we need to install msgpack under ph ...

  3. Yii2 Working with Relational Data at ActiveDataProvider

    Yii2 Working with Relational Data at ActiveDataProvider namespace common\models; use Yii; use yii\ba ...

  4. 一个SAP顾问在美国的这些年

    今天的文章来自我的老乡宋浩,之前作为SAP顾问在美国工作多年.如今即将加入SAP成都研究院S4CRM开发团队.我们都是大邑人. 大邑县隶属于四川省成都市,位于成都平原西部,与邛崃山脉接壤.东与崇州市交 ...

  5. 动态规划专题(五)——斜率优化DP

    前言 斜率优化\(DP\)是难倒我很久的一个算法,我花了很长时间都难以理解.后来,经过无数次的研究加以对一些例题的理解,总算啃下了这根硬骨头. 基本式子 斜率优化\(DP\)的式子略有些复杂,大致可以 ...

  6. 【HHHOJ】NOIP2018 模拟赛(二十五) 解题报告

    点此进入比赛 得分: \(100+100+20=220\)(\(T1\)打了两个小时,以至于\(T3\)没时间打了,无奈交暴力) 排名: \(Rank\ 8\) \(Rating\):\(+19\) ...

  7. C/C++语言补缺 宏- extern "C"-C/C++互调

    1. 宏中的# 宏中的#的功能是将其后面的宏参数进行字符串化操作(Stringizing operator),简单说就是在它引用的宏变量的左右各加上一个双引号. 如定义好#define STRING( ...

  8. 3205: 数组做函数参数--数组元素求和1--C语言

    3205: 数组做函数参数--数组元素求和1--C语言 时间限制: 1 Sec  内存限制: 128 MB提交: 178  解决: 139[提交][状态][讨论版][命题人:smallgyy] 题目描 ...

  9. jquery iCheck 插件

    1 官网:http://www.bootcss.com/p/icheck/#download 2 博客:https://www.cnblogs.com/xcsn/p/6307610.html http ...

  10. UVA1629 Cake slicing

    题目传送门 直接暴力定义f[x1][y1][x2][y2]是使对角为\((x1, y1),(x2, y2)\)这个子矩形满足要求的最短切割线长度 因为转移顺序不好递推,采用记忆化搜索 #include ...