Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from start to end, such that:
- Only one letter can be changed at a time
- Each intermediate word must exist in the dictionary
For example,
Given:
start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]
Return
[ ["hit","hot","dot","dog","cog"], ["hit","hot","lot","log","cog"] ]
Note:
- All words have the same length.
- All words contain only lowercase alphabetic characters.
基本思路:
要找出变化的最短路径,则需要进行一次广度优先搜索,以求得路径最小长度。
再进行一次深度优先搜索,以记录这些能达到目的地的路径。
从一个节点,找到它的邻接点时,通过将每位字符吕a-z逐一进行变化,然后在字典中查找是否存在。
在第一次广度优先搜索之时,记录下每个节点所在树的层次,即距离起始点的最小长度。
然后在深度优先搜索时,利用上面得到的信息。可以避免死循环,以及比淘汰比更短路径更长的路径。
因为每次找邻接点时,检查它是比本节点更上一层的接点。
深度优先搜索,开始与广度优先执行相反的方向。 即如果广度优先执行从start开始, 则深度从end开始。 即自底向上到顶。
因为达到层次1时,0层肯定就是start,无需再计算。 反之,从顶到底的话,因为底部不一定都是end。
而最好的方式,当是广度优先搜索,从end开始。 而深度则从start开始,这样的好处,是省去了最后路径的一个reverse操作。
下面 代码是BFS从start开始,故在最后路径,需要一个reverse操作。
在代码在 leetcode上实际执行时间为476ms。
class Solution {
public:
vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) {
vector<vector<string>> ans;
vector<string> trace;
unordered_map<string, int> visited;
if (bfs(start, end, dict, visited))
dfs(start, end, visited, visited[end], trace, ans);
return ans;
}
bool bfs(string start, string end, const unordered_set<string> &dict, unordered_map<string, int> &visited) {
queue<string> q;
q.push(start);
int level = 0;
visited[start] = level;
bool found = false;
while (!found && !q.empty()) {
++level;
int count = q.size();
while (count--) {
string word = q.front();
q.pop();
for (int i=0; i<word.size(); i++) {
const char ch = word[i];
for (char j='a'; j<='z'; j++) {
word[i] = j;
if (dict.find(word) != dict.end() && visited.find(word) == visited.end()) {
q.push(word);
visited[word] = level;
if (word == end)
found = true;
}
}
word[i] = ch;
}
}
}
return found;
}
void dfs(const string &start, string &end, const unordered_map<string, int> &levelmap, int level, vector<string> &path, vector<vector<string>> &ans) {
auto iter = levelmap.find(end);
if (iter == levelmap.end() || iter->second != level) return;
if (level == 1) {
ans.push_back(path);
ans.back().push_back(end);
ans.back().push_back(start);
reverse(ans.back().begin(), ans.back().end());
return;
}
path.push_back(end);
for (int i=0; i<end.size(); i++) {
const char ch = end[i];
for (char j='a'; j<='z'; j++) {
end[i] = j;
dfs(start, end, levelmap, level-1, path, ans);
}
end[i] = ch;
}
path.pop_back();
}
};
上面在BFS过程中,并未存储邻接点信息。即并未保持,树的父子关系。
在进行DFS过程中,邻接点仍需要进行重新计算。
偿试了一下,在BFS保存邻接点信息。每个结点保存了达到该结点的上一层的所有结点。
发现了BFS写起来要比上面复杂了。不再是那么单纯了: 那个visited,存入时间t和判断时间需要特殊考虑
虽然在visited上下了功夫,保证没有循环回路的产生;但无法避免同一层次结点之间的包含。
故在DFS搜索时,还是需要借助level递减这种方式,剔除,那些走了平行结点的路径。因为这些路径比最短路径要长。
此代码在leetcode上实际执行时间为1160ms。
大大的慢于上面的算法。
而且空间利用比上面更多。
看来此算法不可取。
class Solution {
public:
vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) {
vector<vector<string>> ans;
unordered_map<string, unordered_set<string>> tree;
vector<string> path;
int level;
if (bfs(start, end, dict, tree, level))
dfs(start, end, tree, path, level, ans);
return ans;
}
bool bfs(string start, string end, const unordered_set<string> &dict, unordered_map<string, unordered_set<string>> &tree, int &level) {
unordered_set<string> visited;
queue<string> q;
q.push(end);
bool found = false;
level = 0;
while (!found && !q.empty()) {
int count = q.size();
++level;
while (count--) {
string word = q.front();
q.pop();
if (visited.find(word) != visited.end())
continue;
visited.insert(word);
const string parent = word;
for (int i=0; i<word.size(); i++) {
const char ch = word[i];
for (char j='a'; j<='z'; j++) {
word[i] = j;
if (dict.find(word) != dict.end() && visited.find(word) == visited.end()) {
q.push(word);
tree[word].insert(parent);
if (word == start)
found = true;
}
}
word[i] = ch;
}
}
}
return found;
}
void dfs(string start, string end, unordered_map<string, unordered_set<string>> &tree, vector<string> &path, int level, vector<vector<string>> &ans) {
path.push_back(start);
if (start == end) {
ans.push_back(path);
}
else if (level) {
const auto &parents = tree[start];
for (auto p: parents) {
dfs(p, end, tree, path, level-1, ans);
}
}
path.pop_back();
}
};