解题思路:
双向 宽度搜索
1,BFS的基本思想是为了找到最短路径,这里为了提高搜索效率,采用双向搜索。
2,是从beginWord方向寻找next level,还是从endWord方向寻找next level,取决于哪个方向要找寻的点少
数据结构:
1,unordered_set : unusedWords, wordDict中所有未被visit的words
2,unordered_set : activeWords[startSet], 保存我们即将要用于搜索的words(beginWord 方向,或者endWord方向,取决于size)
3,unordered_set : activeWords[endSet], 与2中 unordered_set 对应的另一边words
如果BFS扩展路径的时候(从activeWords[startSet])到达了一个存在于(activeWords[endSet])的word,那么我们就找到了一个条最短路径
4,unordered_set : activeWords[nextSet],保存BFS从activeWords[StartSet] 扩展出的节点word
5,对BFS的每一步,我们动态的决定应该从哪个方向扩展,主要取决于哪个方向的nodes更少。这样做可以降低搜索复杂度。
在BFS阶段,遍历startSet里面的每一个word,先看endSet里有没有next Level的单词,如果有,则找到一条最短路径,直接返回。否则,再在unusedSet里面找next Level的节点,如果有,加入nextSet,并且从unusedWords中删除
// 860 ms
class Solution {
public:
int ladderLength(string beginWord, string endWord, unordered_set<string>& wordDict) {
int bLen = beginWord.length();
int eLen = endWord.length();
int size = wordDict.size();
if (bLen != eLen || bLen == 0 || size == 0) return 0;
unordered_set<string> unusedWords = wordDict; // words that never visited before
unordered_set<string> activeWords[3];
int startSet = 0;
int endSet = 1;
int nextSet = 2;
int depth = 2;
activeWords[startSet].insert(beginWord);
activeWords[endSet].insert(endWord);
unusedWords.erase(beginWord);
unusedWords.erase(endWord);
while(!activeWords[startSet].empty()){
for (auto it : activeWords[startSet]){
for (auto itend : activeWords[endSet]){
if (match(it, itend)){
return depth;
}
}
vector<string> temp;
for (auto itun : unusedWords){
if (match(it, itun)){
activeWords[nextSet].insert(itun);
temp.push_back(itun);
//这里不能直接用下面这句,因为你迭代器是 unusedWords的,把它的元素删除,迭代器就被玩坏了
//unusedWords.erase(itun);
}
}
for (auto istr : temp){
unusedWords.erase(istr);
}
}
depth++;
swap(startSet, nextSet);
if (activeWords[startSet].size() > activeWords[endSet].size()) {
swap(startSet, endSet);
}
activeWords[nextSet].clear();
}
return 0;
}
bool match(string wordA, string wordB){
if (wordA.length() != wordB.length()) return false;
int diff = 0;
for (int i = 0; i < wordA.length(); ++i){
if (wordA[i] == wordB[i]) continue;
diff++;
if (diff > 1){
return false;
}
}
if (diff == 1) return true;
return false;
}
};
上述算法860ms,改变一下match算法,时间减少的68ms,原因在于
第一种算法,每两个word都要进行一次匹配,O(m*n), 每次匹配要判断 word的长度个次数,所以总时间复杂度时O(m*n*k)
第二种算法,由于unordered_set的查找时间复杂度为O(1), 所以实际的时间复杂度是只跟word的长度有关系,即O(n), 26个字母的替换,常量啦
for (auto it : activeWords[startSet]){
char temp, i;
for(int i = 0; i < it.length(); ++i){
temp = it[i];
for(char j = 'a'; j < 'z'; ++j){
if (temp == j) continue;
it[i] = j;
if (activeWords[endSet].count(it) > 0){
return depth;
}else if (unusedWords.count(it) > 0){
activeWords[nextSet].insert(it);
unusedWords.erase(it);
}
}
it[i] = temp;
}
}