题目链接:链接
题目描述:在wordList中寻找从beginWord到endWord的最短转换序列,要求每次转化只能变换一个字符。
Given:
beginWord = “hit”
endWord = “cog”
wordList = [“hot”,”dot”,”dog”,”lot”,”log”,”cog”]Return
[
[“hit”,”hot”,”dot”,”dog”,”cog”],
[“hit”,”hot”,”lot”,”log”,”cog”]
]
解题思路:一开始以先解题目,后考虑空间、效率的目的,没有过多考虑,写了个DFS + 截枝的思路,算法实现较为简单,但效率过低,在跑list.size() > 100的测试用例时TLE,Solution1代码如下:
Solution1:
//判断是否能从pre转化为cur
bool canChange(string pre, string cur, vector<string>& sub){
for(int i = 0; i < sub.size(); i++){
if(cur == sub[i])return false;
}
//有一个字符不同即可以转化
int count = 0;
for(int i = 0; i < pre.length(); i++){
if(pre[i] != cur[i])count++;
if(count > 1)return false;
}
return count == 1;
}
void findLadder(string end, vector<string>& list, vector<vector<string>>& res, vector<string>& sub){
string cur = sub.back();
//能转换到wordEnd则加入result容器
if(cur == end){
if(res.empty() || res[0].size() == sub.size())
res.push_back(sub);
else if(res[0].size() > sub.size()){
res.clear();
res.push_back(sub);
}
return;
}
//如果转换列表的长度大于当前res容器存储列表的长度,则返回,实现截枝
if(res.size() != 0 && res[0].size() <= sub.size())return;
//递归查找
for(int i = 0; i < list.size(); i++){
if(canChange(cur, list[i], sub)){
sub.push_back(list[i]);
findLadder(end, list, res, sub);
sub.pop_back();
}
}
}
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
vector<vector<string>> res;
vector<string> sub;
sub.push_back(beginWord);
findLadder(endWord, wordList, res, sub);
return res;
}
算法分析:
1. 编写代码时就注意到效率最低肯定在递归查找,每次递归都进行O(n)时间寻找,效率极低,可建立unordered_map映射,保存所有已搜寻过的可转换序列加入列表,先从列表查找;
2. canChange函数判断是否能从pre转化为cur,vector数据结构的遍历也为O(n1)时间;可用红黑树实现的unordered_set数据结构,查找效率为O(log(n1));
3. 算法本身性质问题,深度搜索不保证第一次搜寻的就是最短序列,广度优先搜寻所有最优解更合适。
针对以上问题,进行改进:现广度优先搜索生成树,后进行路径生成,得解!但是,在更大更大更大数量的测试下还是差在时间问题!!!再次思考数据结构的问题···通过漫长漫长的思考以及网上的学习,发现可进行以下思路进行改进:
1.运用两个unordered_set数据结构,保存生成树前一高度与当前高度的字符串,交替变换,可将已在树中的string从list中删除,保证父树不重复出现在子树节点中;如(1)a->b,b->c,(2)a->c的情况,当广度搜寻完c时,把c删除,截去情况(1);
2.判断子节点方面,逐位置改变父节点的字符进行判断,建树效率为str.length*26*log(n)(平衡二叉树查找),而Solution1对list遍历进行查找,其效率为stri.length*n;
奋战近1.5天,Solution2解题如下(176ms AC):
网上见有88ms,56msAC的方法,休息片刻,再继续学习~总而言之,编程量较大的题目思考收获挺多的~
void findPath(string now, string end, vector<string>& sub,
unordered_map<string, vector<string>>& path, vector<vector<string>>& res){
if(now == end){
sub.push_back(now);
vector<string> t = sub;
reverse(t.begin(), t.end());
res.push_back(t);
sub.pop_back();
return;
}
for(int i = 0; i < path[now].size(); i++){
sub.push_back(now);
findPath(path[now][i], end, sub, path, res);
sub.pop_back();
}
}
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
vector<vector<string>> res;
unordered_set<string> list(wordList.begin(), wordList.end());
unordered_map<string, vector<string>> path;
vector<unordered_set<string>> layer(2);
int cur = 0;
int pre = 1;
list.insert(beginWord);
layer[cur].insert(beginWord);
while(true){
swap(cur, pre);
layer[cur].clear();
for(unordered_set<string>::iterator iter = layer[pre].begin(); iter != layer[pre].end(); iter++){
list.erase(*iter);
string t = *iter;
for(int i = 0; i < (*iter).size(); i++){
string word = *iter;
int stop = word[i] - 'a';
for(int j = (stop + 1)%26; j != stop; j = (j+1) % 26){
word[i] = j + 'a';
//layer[pre].find(word) == layer[pre].end()除去寻找过程中已保存在pre的情况
if(list.find(word) != list.end() && layer[pre].find(word) == layer[pre].end()){
layer[cur].insert(word);
path[word].push_back(*iter);
}
}
}
}
if(layer[cur].size() == 0){
return res;
}
if(layer[cur].find(endWord) != layer[cur].end()){
break;
}
}
vector<string> sub;
//从endWord到beginWord查找
findPath(endWord, beginWord, sub, path, res);
return res;
}