学习资料:力扣、《LeetCode 101 - A LeetCode Grinding Guide (C Version)》、力扣liweiwei1419用户等等。
目录
法二:DFS + BFS 双向搜索(two-end BFS)估计这个是正解
1.下马威,最难最综合的广搜深搜题。
这题实在太强了,估计在我未来一年做的题目里面都能排前十,也非常妙,必须放第一个说。题目链接: 力扣
而且力扣官方后来提高样例的难度,导致法一法二只能对34/36,只有法三C
法一:DFS为主BFS为辅助
第一阶段分析
从题目其实可以任容易看出,用深度搜索似乎是一个比较明显的解法,我利用回溯来控制path和result,加上只变化一个字母的条件,用回溯是一定可解出来的。
但是毫无疑问超时太多,只能对两三个样例。
那我们可以加两个简单的,也是容易想到的剪枝,比如:
1.因为我们要找的是最短的路径。但是事先我们并不知道最短的路径是多少,我们需要一个全局变量来保存当前找到的路径的长度。如果找到的新的路径的长度比之前的路径短,就把之前的结果清空,重新找,如果是最小的长度,就加入到结果中。
2.当前的长度到达了 min,还是没有到达结束单词就提前结束
同时,我们判断只变化一个字母的函数也可以加快:
第一种,遍历
wordList
来判断每个单词和当前单词是否只有一个字母不同。类似于下面的代码,O(m*n),虽然比较自然但是慢。
private boolean oneChanged(String beginWord, String curWord) {
int count = 0;
for (int i = 0; i < beginWord.length(); i++) {
if (beginWord.charAt(i) != curWord.charAt(i)) {
count++;
}
if (count == 2) {
return false;
}
}
return count == 1;
}
优化:将要找的节点单词的每个位置换一个字符,然后看更改后的单词在不在
wordList
中,利用count函数,O(26n),不过count最坏也是O(n),只能说是略微优化
//更快的找邻居,因为如果要遍历去找是mn,这样改变字母去对应
//利用count是26n,会快很多
vector<string> getNeighbours(string s, vector<string>& wordList)
{
vector<string> rs;
for (char ch = 'a'; ch <= 'z'; ++ch) {
for (int i = 0; i < s.size(); ++i) {
if (s[i] == ch)continue;
char od = s[i];
s[i] = ch;
if (count(wordList.begin(), wordList.end(), s)) {
rs.push_back(s);
}
s[i] = od;
}
}
return rs;
}
回溯的函数比较简单,对回溯已经很熟练了,加上剪枝的代码如下:(注意注释)
vector<vector<string>> rs;
vector<string> path;
int min = 1 << 20;//足够的大数
void backtracking(string temp, string endWord, vector<string>& wordList) {
//剪枝加收获结果
if (temp == endWord) {
path.push_back(temp);
if (path.size() < min) {
rs.clear();//这步很关键,说明之前更长的结果已经没用了
min = path.size();
rs.push_back(path);
}
else if (path.size() == min) {
rs.push_back(path);//相等要加入
}
path.pop_back();
return;
}
if (path.size() >= min) {
return;//没有找到最后一个,长度也已经大于等于,自然剪枝
}
//获取该节点的邻居,准备for横向,递归纵向遍历
vector<string> neighbours = getNeighbours(temp, wordList);
for (const string& neb : neighbours) {
if (count(path.begin(), path.end(), neb))continue;//如果path里面已经有这个元素自然跳过
path.push_back(temp);
backtracking(neb, endWord, wordList);
path.pop_back();//回溯
}
}
所以我们可以写出19/36的一份慢但是对的代码:
class Solution {
public:
vector<vector<string>> rs;
vector<string> path;
int min = 1 << 20;//足够的大数
void backtracking(string temp, string endWord, vector<string>& wordList) {
//剪枝加收获结果
if (temp == endWord) {
path.push_back(temp);
if (path.size() < min) {
rs.clear();//这步很关键,说明之前更长的结果已经没用了
min = path.size();
rs.push_back(path);
}
else if (path.size() == min) {
rs.push_back(path);//相等要加入
}
path.pop_back();
return;
}
if (path.size() >= min) {
return;//没有找到最后一个,长度也已经大于等于,自然剪枝
}
//获取该节点的邻居,准备for横向,递归纵向遍历
vector<string> neighbours = getNeighbours(temp, wordList);
for (const string& neb : neighbours) {
if (count(path.begin(), path.end(), neb))continue;//如果path里面已经有这个元素自然跳过
path.push_back(temp);
backtracking(neb, endWord, wordList);
path.pop_back();//回溯
}
}
//更快的找邻居,因为如果要遍历去找是mn,这样改变字母去对应
//利用count是26n,会快很多
vector<string> getNeighbours(string s, vector<string>& wordList)
{
vector<string> rs;
for (char ch = 'a'; ch <= 'z'; ++ch) {
for (int i = 0; i < s.size(); ++i) {
if (s[i] == ch)continue;
char od = s[i];
s[i] = ch;
if (count(wordList.begin(), wordList.end(), s)) {
rs.push_back(s);
}
s[i] = od;
}
}
return rs;
}
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
backtracking(beginWord, endWord, wordList);
return rs;
}
};
第二阶段:加速
第一处优化:
这个还是很巧妙的,往往需要认真思考才能明白
第二处:
这个问题我们第一阶段的部分考虑了一丢丢:
但我们只考虑了当前路径是否含有该单词,而就像上上图表示的,其他路径之前已经考虑过了当前单词,我们也是可以跳过的
方法:再利用一个
HashMap
,记为distance
变量。在BFS
的过程中,把第一次遇到的单词当前的层数存起来。之后遇到也不进行更新
代码:(还是有点慢)
class Solution {
public:
vector<vector<string>> rs;
vector<string> path;
void backtracking(string temp, string