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 transformed word must exist in the word list. Note that beginWord is not a transformed word.
Note:
- Return 0 if there is no such transformation sequence.
- All words have the same length.
- All words contain only lowercase alphabetic characters.
- You may assume no duplicates in the word list.
- You may assume beginWord and endWord are non-empty and are not the same.
Example 1:
Input:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
Output: 5
Explanation: As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",
return its length 5.
Example 2:
Input:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
Output: 0
Explanation: The endWord "cog" is not in wordList, therefore no possible transformation.
题目链接:https://leetcode-cn.com/problems/word-ladder/
思路
做过perfect square这道题,就知道本题可以采用类似思路,转为图问题找最短路径。
但本题搜索空间扩大了,连通条件也边复杂了,需要多一些时间复杂度上的思考。
法一:BFS
遍历逻辑:每从队列取出一个单词,在单词表中找到相差1个字母的未访问单词加入队列。
注意:不能一边遍历,一边把已访问的元素从表中删除。这会导致一旦中间的元素被删,后面就会出现有元素遍历不到。因此要有变量记录元素是否遍历过。
问题:当单词表较大时,代码会超时。
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
if(wordList.size()<=0 || beginWord==endWord) return 0;
queue< pair<int, int>> record;
record.push(make_pair(-1, 1));
bool visit[wordList.size()] = {false};
while(!record.empty()){
auto tmp = record.front();
record.pop();
string s1;
if(tmp.first==-1){
s1 = beginWord;
}else{
s1 = wordList[tmp.first];
}
if(s1==endWord) return tmp.second;
for(int i=0; i<wordList.size(); ++i){
string s2;
s2 = wordList[i];
if(!visit[i] && isConnect(s1, s2)){
record.push(make_pair(i, tmp.second+1));
visit[i] = true;
}
}
}
return 0;
}
bool isConnect(string s1, string s2){
int dif = 0;
for(int i=0; i<s1.size(); ++i){
if(dif>1) break;
if(s1[i]!=s2[i]) ++dif;
}
if(dif>1) return false;
else return true;
}
};
改进:当判断连通时,不去遍历vector,而另一种遍历逻辑:先构造,再去差有没有;同时把遍历过的元素直接从列表中删除。
问题:由于vector查找慢,依旧超时。
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
if(wordList.size()<=0 || beginWord==endWord) return 0;
queue< pair<string, int>> record;
record.push(make_pair(beginWord, 1));
while(!record.empty()){
auto tmp = record.front();
record.pop();
string s1 = tmp.first;
if(s1==endWord) return tmp.second;
for(int i=0; i<s1.size(); ++i){
string s2 = s1;
for(char c='a'; c<='z'; ++c){
if(c==s2[i]) continue;
s2[i] = c;
auto it = find(wordList.begin(), wordList.end(), s2);
if(it!=wordList.end()){
record.push(make_pair(s2, tmp.second+1));
wordList.erase(it);
}
}
}
}
return 0;
}
};
改进:将vector装进unordered_set加快查找速度。
现在可以通过。
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
if(wordList.size()<=0 || beginWord==endWord) return 0;
unordered_set<string> list(wordList.begin(), wordList.end());
queue< pair<string, int>> record;
record.push(make_pair(beginWord, 1));
while(!record.empty()){
auto tmp = record.front();
record.pop();
string s1 = tmp.first;
if(s1==endWord) return tmp.second;
for(int i=0; i<s1.size(); ++i){
string s2 = s1;
for(char c='a'; c<='z'; ++c){
if(c==s2[i]) continue;
s2[i] = c;
auto it = list.find(s2);
if(it!=list.end()){
record.push(make_pair(s2, tmp.second+1));
list.erase(it);
}
}
}
}
return 0;
}
};
法二:双向BFS
看分享学会的。
提出的思路是:当从一个点出发走向另一个点时,每多走一步,可能的路径条数就会不断增多(搜索空间扩大)——就是一棵数从根节点不断长出叶子节点。而如果同时从目标节点出发,两边同时向中间走,则可以缩小搜索空间。
!注意编程的坑:beginWord和endWord在初始时的visit值也要记录!!亲测不计不给过特殊样例。。
代码一:找连通节点时对单词表逐个遍历
逻辑查找费时。
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
if(wordList.size()<=0 || beginWord==endWord
|| find(wordList.begin(), wordList.end(), endWord)==wordList.end()) return 0;
queue<string> record1, record2;
record1.push(beginWord);
record2.push(endWord);
unordered_map<string, int> visit;
visit[beginWord] = 1;
visit[endWord] = 2;
int step = 0;
while(!record1.empty() && !record2.empty()){
++step;
int flag = (record1.size()<=record2.size())?1:2;
queue<string> &record = (flag==1)?record1:record2;
int len = record.size();
for(int i=0; i<len; ++i){
auto s1 = record.front();
record.pop();
if(visit[s1]==3) return step;
for(int j=0; j<wordList.size(); ++j){
string s2 = wordList[j];
if(visit[s2]!=flag && isConnect(s1, s2)){
record.push(s2);
visit[s2] = (visit[s2]==0)?flag:3;
}
}
}
}
return 0;
}
bool isConnect(string s1, string s2){
int dif = 0;
for(int i=0; i<s1.size(); ++i){
if(dif>1) break;
if(s1[i]!=s2[i]) ++dif;
}
if(dif==1){
return true;
}
else return false;
}
};
代码二:找连通节点时先构造再查找
点1:在visit创建时将单词表初始化,用于构造单词后的查找,提升查找速度。
点2:不能像单向BFS那样访问一个元素就把他从单词表删除,因为现在元素有3种状态:未访问/正向访问/逆向访问。
在数据较多的情况下运行时间大大减少。
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
if(wordList.size()<=0 || beginWord==endWord
|| find(wordList.begin(), wordList.end(), endWord)==wordList.end()) return 0;
queue<string> record1, record2;
record1.push(beginWord);
record2.push(endWord);
unordered_map<string, int> visit;
for(const auto &word: wordList)
visit[word]=0;
visit[beginWord] = 1;
visit[endWord] = 2;
int step = 0;
while(!record1.empty() && !record2.empty()){
++step;
int flag = (record1.size()<=record2.size())?1:2;
queue<string> &record = (flag==1)?record1:record2;
int len = record.size();
for(int i=0; i<len; ++i){
auto s1 = record.front();
record.pop();
if(visit[s1]==3) return step;
for(int j=0; j<s1.size(); ++j){
for(char c='a'; c<='z'; ++c){
string s2 = s1;
if(s2[j]==c) continue;
s2[j] = c;
if(visit.count(s2)>0 && visit[s2]!=flag){
record.push(s2);
visit[s2] = (visit[s2]==0)?flag:3;
}
}
}
}
}
return 0;
}
};
参考
1.双向BFS:https://leetcode-cn.com/problems/word-ladder/solution/shuang-xiang-bfs-by-joy-teng/