Given two words (beginWord and endWord), and a dictionary’s word list, find the length of shortest transformation sequence from beginWord to endWord, such that:
- Only one letter can be changed at a time
- Each intermediate word must exist in the word list
For example,
Given: beginWord = “hit”
endWord = “cog”
wordList = [“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.
解题思路:
可以构造一张图。如果两个word之间只相差一个字母,那么我们认为这两个字母之间是连通的,可以从一个word转换为另一个word。而要查找两个word之间的最短路径,在刚才构建的那张图的基础上使用广度优先遍历,那么第一个返回的路径自然就是最短路径。
比较有迷惑性的是这个题目给出的输入,wordList以unordered_set的形式来给出。unordered_set基于hash表实现,能够在O(1)时间内确认某个对象是否在set中,但无法顺序遍历来构造图。注意给出的条件是所有word只包含小写字母,那么我们只需遍历当前word中的每个字母并改变它,然后判断改变后的字母是否在wordList中。这样做的好处是,字母的数目是有限的,我们需要判断length(word)*26次就能遍历所有可能性来获得某个特定word所邻接的所有word。而如果使用邻接表,需要O(n^2)的额外空间,n为wordList中word的数目。在字典特别巨大的时候,对空间的开销是巨大的,而且也需要O(n^2)的时间来遍历构造邻接表。
注意点:
1. 在图遍历的时候,最大的问题是环的处理。我们可以用一个数组记录某个节点是否被访问,这里用了另外一种方法,那就是把已经访问到的word从wordList中删除。这会修改输入的数组,如果不想修改,可以先复制一份,在备份中修改。
2. 删除word的时机也需要考虑。我一开始是在中间word出队列的时候再删除的,但这会造成在中间word出队列之前,后续的邻接word会将word又重新加入队尾,引起循环添加。因此,在word进入队列的时候就该将它从wordList中删除。
3. 返回值为转换链的单词数,那么需要记录当前访问元素在广度优先遍历中的层数,我用了一个struct来将层数和string包裹起来,这是一种做法,另外也可以在每层的尾部插入一个标记如空字符串或非小写字母构成的字符串,来判断当前层数是否加1.这种实现会比我当前使用的方式更快一些。
实现代码(时间448ms,使用标记来记录层数时间约300ms,应该还有更快的方法,以后可以再看看):
struct node {
int level;
string str;
node(int l, string s) : level(l), str(s) {}
};
class Solution {
public:
int ladderLength(string beginWord, string endWord, unordered_set<string>& wordList) {
queue<node> mid_words;
node begin(1, beginWord);
mid_words.push(begin);
if(wordList.find(beginWord) != wordList.end())
wordList.erase(beginWord);
while(!mid_words.empty()) {
node temp = mid_words.front();
string word(temp.str), str(word);
int cur_level = temp.level+1;
mid_words.pop();
for(int i = 0; i < word.size(); ++i) {
for(int j = 0; j < 26; ++j) {
str = word;
str[i] = 'a' + j;
if(str != word && wordList.find(str) != wordList.end()) {
if(str == endWord)
return cur_level;
else {
node new_node(cur_level, str);
mid_words.push(new_node);
wordList.erase(str);
}
}
}
}
}
return 0;
}
};