题目给定两个单词(start和end)和由一组单词组成的词典,要求找到从start到end所有的变形序列,满足:
举例来说,
start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]
返回
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]
挑战一下Hard的题目,通过率很低只有13.9%,题目给出了一些节点和构成图的方法,相当于给出了一个图,如果只需要求出其中的一条最短路,很显然用Dijkstra算法就能轻松解决,但是题目要求的是给出所有可能的变形序列,就需要特殊的解决方法。
思路上讲,Dijkstra算法可以看做是一种特殊的BFS,那么想求出所有路径很自然就想到使用BFS,使用类似穷举的方法,直到从start走到end。因为需要所有的路径,使用一个vector存储pre数组肯定不够,我选择使用一个由set组成的vector的来存储nexts,将由一个顶点能到达的所有顶点都放在对应的nexts中,然后nexts相当于组成了一个简化的图,包含所有可能的路径(有很多无效的路径),这时再经过一次DFS得出所有路径,方法类似于我的上一篇博客《Leetcode 526 357》中的做法,使用DFS穷举和判断,从start开始提取出所有能到达next的ladders。
在具体实现上,我选择先把整个图构建出来,因为假设共有n个单词(包括start),每个单词都有m个字母,这样共需要m[n(n-1)/2]次字母间的比较,题目给出的n和m应该不会太大,这个复杂度可以接受,相比边BFS边建图,这样的做法思路更清晰,理解性更强,缺点是牺牲了空间。
开始实现后,我发现本题的主要问题还来自超时(Time Limit Exceed)和超出内存限制(Memory Limit Exceed),如何进行有效的时空的优化也是本题的难点。
string类型毕竟比较大,相互比较也会很耗时,如果直接放进set或map中显然又费空间又费时间,整个图的结构会显得很重。我选择把string存储在一个vector中,再用一个map来记录一个string的索引值,这样不论是根据索引值找string,还是根据string找索引值都很快。在保存的图结构中,只需要存储索引值之间的关系即可,所有BFS和DFS的过程都不需要和真正的string打交道,所有的判断也只需要简单地在int之间进行。
这样的做法能节省相当的空间,很多东西用vector就能实现,时间上也会好一些,在编码上,除了构建图的过程,其他的代码都会简单一些,不必担心map抛出异常的处理。
时间上的优化主要集中在如何减少BFS的情况,相应的也就能减少nexts的大小,显然也就优化了DFS的时间。
众所周知,BFS过程在一个图的每一层进行,在这个简单的无权无向图中,层是按照远离start的方向,按照距离start的距离区分,距离相等的顶点是一层。那么问题就集中在当前正在访问的层和下一层之间,当前层的距离一定低于下一层,那么如果一个顶点在当前层里出现,那它在下一层中就没有出现的必要,因为无论如何,在下一层出现一定会产生一个效果等价但长度更长的路径,这是没有必要的。如果删除这些,那么DFS时也能避免搜索很多无效路径,产生额外的好处。
常规的BFS并不区分当前层和下一层,只用一个队列将所有节点入队,而我的做法是使用两个队列,使用curr_queue指针指向当前正在访问的队列,使用next_queue指针来指向访问时新添加的顶点的队列,在curr_queue被访问完变成空时,再把curr_queue指针和next_queue指针的值交换,curr_queue就变成了完整的当前层队列,而next_queue变成空,这里就可以将curr_queue中的所有节点标记为visited,下次添加节点到next_queue时就不会添加curr_queue中的节点进去。
这样做也有一个额外的好处,当在当前层搜索到end的时候,next_queue就可以很方便地直接宣布作废(不再交换),只把当前层搜索完即可。
实现上,因为STL中的queue不能访问其中的内容,这里只能选择STL中的deque来实现,刚才提到使用指针来实现显式地切换当前层和下一层,只需要交换指针指向的deque,不需要任何实际的复制操作,时间上没有增加什么复杂度,空间上也只是将一个长queue分成两个,也没有增加什么复杂度,额外的复杂度主要来自选用deque实现队列,增加了存储queue的空间和添加删除操作的时间。
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
template<class T>
void print2DMatrix(const vector<vector >& matrix) {
for (unsigned int i = 0; i < matrix.size(); ++i) {
if (!matrix[i].empty())
cout << matrix[i].front();
for (unsigned int j = 1; j < matrix[i].size(); ++j)
cout << " " << matrix[i][j];
cout << endl;
}
}
template <class InputIterator>
void printContainer(InputIterator first, InputIterator last) {
if (first != last)
cout << *first++;
while (first != last)
cout << " " << *first++;
cout << endl;
}
template<class Vex>
class Graph {
public:
vector<unordered_set<int> > adj;
vector VLookup;
unordered_mapint > VIndexLookup;
int getVexIndex(const Vex& v) const {
assert(VIndexLookup.count(v) != 0);
return VIndexLookup.at(v);
}
inline unsigned int size() const {
return VLookup.size();
}
bool addVex(const Vex& v) {
if (VIndexLookup.count(v) != 0)
return false;
VIndexLookup[v] = VLookup.size();
VLookup.push_back(v);
adj.push_back(unordered_set<int>());
return true;
}
void addEdge(const Vex& start, const Vex& end) {
const int startIndex = getVexIndex(start);
const int endIndex = getVexIndex(end);
adj[startIndex].insert(endIndex);
}
void print() const {
for (unsigned int u = 0; u < size(); ++u) {
cout << VLookup[u] << ": ";
for (const int v : adj[u])
cout << VLookup[v] << " ";
cout << endl;
}
}
};
class Solution {
bool BFS(const Graph<string>& G, const int startIndex, const int endIndex,
vector<unordered_set<int> >& nexts) {
nexts.assign(G.size(), unordered_set<int>());
bool isFound = false;
vector<bool> visited(G.size(), false);
deque<int> bfs_queue[2];
deque<int>* curr_queue = &bfs_queue[0];
deque<int>* next_queue = &bfs_queue[1];
curr_queue->push_back(startIndex);
visited[startIndex] = true;
while (!curr_queue->empty()) {
const int u = curr_queue->front();
/* visit bfs_queue.front()*/
if (u == endIndex) {
isFound = true;
// cout << "found" << endl;
}
if (!isFound) {
for (const int v : G.adj[u]) {
if (!visited[v]) {
next_queue->push_back(v);
nexts[u].insert(v);
}
}
}
curr_queue->pop_front();
if (curr_queue->empty() && !isFound) {
swap(curr_queue, next_queue);
for (const int i : *curr_queue)
visited[i] = true;
}
}
// for (const auto& s : nexts)
// printContainer(s.begin(), s.end());
return isFound;
}
void DFS(const int startIndex, const int endIndex,
const vector<unordered_set<int> >& nexts,
vector<int>& path, vector<vector<int> >& paths) {
if (startIndex == endIndex)
paths.push_back(path);
else {
for (const int idx : nexts[startIndex]) {
path.push_back(idx);
DFS(idx, endIndex, nexts, path, paths);
path.pop_back();
}
}
}
public:
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
vector<vector<string> > ladders;
if (find(wordList.begin(), wordList.end(), endWord) == wordList.end())
return ladders;
Graph<string> G;
G.addVex(beginWord);
for (const string& w : wordList)
G.addVex(w);
for (unsigned int i = 0; i < G.VLookup.size(); ++i) {
for (unsigned int j = i + 1; j < G.VLookup.size(); ++j) {
const string& w1 = G.VLookup[i];
const string& w2 = G.VLookup[j];
int dist = 0;
for (unsigned int k = 0; k < w1.size(); ++k)
if (w1[k] != w2[k])
dist++;
if (dist == 1) {
G.addEdge(w1, w2);
G.addEdge(w2, w1);
}
}
}
G.print();
const int startIndex = G.getVexIndex(beginWord);
const int endIndex = G.getVexIndex(endWord);
vector<unordered_set<int> > nexts;
bool isFound = BFS(G, startIndex, endIndex, nexts);
if (!isFound)
return ladders;
vector<int> path;
vector<vector<int> > paths;
path.push_back(startIndex);
DFS(startIndex, endIndex, nexts, path, paths);
for (const auto& path : paths) {
vector<string> v;
for (const int i : path)
v.push_back(G.VLookup[i]);
ladders.push_back(v);
}
return ladders;
}
};
int main(int argc, char const *argv[]) {
Solution s;
// string beginWord = "hit";
// string endWord = "cog";
// vector wordList({"hot", "dot", "dog", "lot", "log", "cog"});
string beginWord = "qa";
string endWord = "sq";
vector<string> wordList({ "si", "go", "se", "cm", "so", "ph", "mt", "db", "mb", "sb", "kr", "ln",
"tm", "le", "av", "sm", "ar", "ci", "ca", "br", "ti", "ba", "to", "ra",
"fa", "yo", "ow", "sn", "ya", "cr", "po", "fe", "ho", "ma", "re", "or",
"rn", "au", "ur", "rh", "sr", "tc", "lt", "lo", "as", "fr", "nb", "yb",
"if", "pb", "ge", "th", "pm", "rb", "sh", "co", "ga", "li", "ha", "hz",
"no", "bi", "di", "hi", "qa", "pi", "os", "uh", "wm", "an", "me", "mo",
"na", "la", "st", "er", "sc", "ne", "mn", "mi", "am", "ex", "pt", "io",
"be", "fm", "ta", "tb", "ni", "mr", "pa", "he", "lr", "sq", "ye"
});
vector<vector<string> > ladders;
ladders = s.findLadders(beginWord, endWord, wordList);
print2DMatrix(ladders);
system("pause");
return 0;
}
代码包含测试使用的main函数和打印函数,还有一个使用template编写的Graph辅助类,用于构建无权图。
第一次在LeetCode上交这么长的代码,去除main函数和打印相关的函数,最后提交了159行代码,最后运行时间是369ms,解决了问题。回顾来看,思路和优化技巧都很直接,看到答案中的大神有使用双向BFS解题的,应该会快不少,觉得佩服佩服,还需要继续努力。