Leetcode 126 单词接龙Ⅱ 跑某个测试用例时 超出时间限制
来源:6-5 BFS和图的最短路径 Perfect Squares
慕村510262
2022-08-02
花了好几个小时来做这题,我尝试使用了三种方法来答这题,跑这测试用例时都超时了:
① 单向 BFS
② BFS(计算最短转换序列中的单词数目)+ DFS(回溯构建这样长度的所有路径)
③ 双向 BFS
最后没点子了,就拷贝了老师代码仓库中的代码试一下,也是倒在这个测试用例
这测试用例给我搞糊涂了,不知有啥解法可以通过这个测试用例
附上我第三种解法的代码:
class Solution {
public:
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
unordered_set<string> dict(wordList.cbegin(), wordList.cend());
if (dict.find(endWord) == dict.cend()) return {};
unordered_multimap<string, vector<string>> head, tail, *phead, *ptail;
head.insert({ beginWord, initializer_list<string>{ beginWord } });
tail.insert({ endWord, initializer_list<string>{ endWord } });
dict.erase(beginWord);
dict.erase(endWord);
vector<vector<string>> ans;
while (!head.empty() && !tail.empty()) {
if (head.size() < tail.size()) {
phead = &head;
ptail = &tail;
} else {
phead = &tail;
ptail = &head;
}
unordered_set<string> del_word_set;
unordered_multimap<string, vector<string>> temp;
for (auto &pair: *phead) {
string word = pair.first;
for (int i = 0; i < word.size(); i++) {
char c = word[i];
for (int j = 'a'; j <= 'z'; j++) {
word[i] = j;
if (ptail->find(word) != ptail->end()) {
auto &path1 = pair.second;
auto range = ptail->equal_range(word);
for (auto iter = range.first; iter != range.second; ++iter) {
auto &path2 = iter->second;
if (phead == &head) {
ans.push_back(path1);
ans.back().insert(ans.back().end(), path2.crbegin(), path2.crend());
} else {
ans.push_back(path2);
ans.back().insert(ans.back().end(), path1.crbegin(), path1.crend());
}
}
} else {
if (dict.count(word) == 1) {
del_word_set.insert(word);
vector<string> new_path = pair.second;
new_path.push_back(word);
temp.insert({ word, new_path });
}
}
}
word[i] = c;
}
}
if (!ans.empty()) {
break;
}
for (auto &del_word: del_word_set) {
dict.erase(del_word);
}
phead->swap(temp);
}
return ans;
}
};
1回答
-
liuyubobobo
2022-08-10
我测试了一遍,现在直接使用 bfs + 正向回溯 确实是超时的。力扣添加了测试用例。不过这也 make sense,毕竟是一个 hard 的问题,如果用最直接的思路就搞定了就太简单了:)
==========
要想“优化”,关键肯定不是 bfs。因为这个问题最多 500 个节点,边最多 500 * 500 也就是 25w 这个量级。bfs 是 O(V+E) 的,处理 25w 这个量级的数据肯定不会超时。所以把 bfs 改成双向 bfs 意义不大。(双向 bfs 减小一半的搜索量)。
这个问题超时的关键一定在回溯所有解的地方。
==========
在描述我的解决办法之前,我想先强调一下,这个问题其实出的并不好。因为一张图上不同最短路径的数量,可以是指数级的。
比如:
2 / \ 1 4 \ / 3
这张图中,从 1 到 4 的最短路径的条数有 2 条;
2 5 / \ / \ 1 4 7 \ / \ / 3 6
这张图中,从 1 到 7 的最短路径的条数有 4 条;
2 5 8 / \ / \ / \ 1 4 7 10 \ / \ / \ / 3 6 9
这张图中,从 1 到 10 的最短路径的条数有 8 条;
可以看到,我按照这个规律,每在途中添加 3 个节点,从 1 到最后一个节点的最短路径的条数就翻一倍,是指数级上升的。也正是因为这个原因,大多数问题只会问最短路径的长度,而不会问具体的最短路径有哪些。因为理论上,枚举一张图的所有最短路径是指数级的。
而这个问题就再让我们做这个枚举,所以他的理论复杂度是指数级的。我只能说它确保了测试用例中不存在这个指数级结果的可能。因此,是测试数据相关的(而非问题相关的),这是我说这个问题不好的原因。
==========
最后,如何不超时?非常简单。在回溯解的时候,从结尾出发,反向走到开头。因为从开头出发,可能会走到很多走不到结尾的“路”。但是从结尾出发,保证了走的每一条路,都是能从开头走过来的,节省了很多多余的搜索。
具体表现在代码上(我已经对 github 上的代码做了更新):https://github.com/liuyubobobo/Play-Leetcode/blob/master/0001-0500/0126-Word-Ladder-II/cpp-0126/main.cpp
48 行,初始路径的起点是 wordList[end]
49 行回溯函数的调用,初始 cur 传入的是 end;end 是 begin。也就是在找从 end 到 begin 的路径。(反向的)
88 行找到一条可以“走”的边条件是:dis[i] == dis[cur] - 1(而非 +1)。因为是反向的。
83 行找到一个路径以后,需要对这个路径 reverse,存到最后要返回的解中。
继续加油!:)
00
相似问题