题意如下:
给定一个开始字符串和结束字符串以及含有大量字符串的字典,要求通过一定规则,能从开始字符串变换到结束字符串,求其最小变换字符串数目,该规则如下:
1 从开始字符串开始,每次只变化一个字母
2 改变一个字母后的字符串必须在字典内
注意:
1 如果没有这样的序列返回0
2 所有字符串相同长度
3 所有字符串只有小写字母
4 字典里每个字符串不重复
5 开始和结束字符串非空且不同
例如:
beginWord = “hit”
endWord = “cog”
wordList = [“hot”,”dot”,”dog”,”lot”,”log”]
+-----+
// +-------------+ hit +--------------+
// | +--+--+ |
// | | |
// +--v--+ +--v--+ +--v--+
// | dit | +-----+ hot +---+ | hig |
// +--+--+ | +-----+ | +--+--+
// | | | |
// | +--v--+ +--v--+ +--v--+
// +----> dot | | lot | | dig |
// +--+--+ +--+--+ +--+--+
// | | |
// +--v--+ +--v--+ |
// +----> dog | | log | |
// | +--+--+ +--+--+ |
// | | | |
// | | +--v--+ | |
// | +--->| cog |<-- + |
// | +-----+ |
// | |
// | |
// +----------------------------------+
Return
[
[“hit”,”hot”,”dot”,”dog”,”cog”],
[“hit”,”hot”,”lot”,”log”,”cog”]
]
思路:
很明显,这是一个求最短路径问题,由图论知识,可以通过BFS或者Dijkstra算法完成,BFS主要适用与无权图,而后者可以用于有权图, 可以看出在本题中两个字符串之间是无权重的, 也就是如果连通就是1, 不联通就是无穷. BFS和Dijkstra的区别是前者的时间复杂度是O(n), 后者最多优化到O(m log n), 所以如果条件成立一般选择BFS要更好。
先把起点加到队列中,然后从字典中删除该元素防止队列对同一元素的重复添加,之后次将字典中与队首距离为1的字符串加进队列, 直到最后出队列的是终点字符串:
这里选择与前一个字符串相差为1的标准主要两个:
1 先根据前一个字符串,再通过26个字符变化,查看是否存在于字典,其时间复杂度是O(k log n), 其中k为单个字符串长度, n为字典长度;
2 寻找与前一个字符串相距为1的的字典中另一个字符串每次时间复杂度是O(n),
考虑到字典中字符串数目庞大即n很大,故选择方法1。
需要注意的地方就是,如果我们曾经遍历过某个元素,我会将其从字典中删除,以防以后再次遍历到这个元素,这里有几种情况:
1.以后再也遍历不到这个元素,那么我们删除它当然没有任何问题。
2.我们以后会遍历到该元素,又分为两种情况:
(1)在本层我们就能遍历到该元素。也就是说,我们到达这个元素有两条路径,而且它们都是最短路径。
举一个例子应该比较容易理解:比如hot->hog->dog->dig和hot->dot->dog->dig,那么在第一次遍历距离hot为1的元素时,我们找到了hog和dot。对hog遍历时,我们找到了dog,并且将其从字典中删除。那么在遍历距离dot为1的元素时,我们实际上是找不到dog的,因为已经被删除了。对于本题来说,是没有什么影响的,因为到dog距离都是3,到dig距离都是4。但是后面我们做word ladder 2的时候,如果没有考虑这个情况,将是非常致命的(126题就是这种),因为题目要求输出最短路径的所有情况。
(2)在更下层我们才能够遍历到该元素。比如hot->dot->dog->dig和hot->hat->dat->dag->dog->dig,如果第一次我们找到了dog并且将其删除,那么第二次我们实际上是找不到这个元素的。这样对于本题来说,没有任何影响。对于word ladder 2来说,因为也是要输出最短路径,所以也不会有任何影响。但是倘若我们要输出从起点到终点的所有路径,那么我们就要小心这种情况了。
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
set<string>wordList1(wordList.begin(),wordList.end());//去重
wordList1.insert(beginWord);
queue< pair<string,int> >que;
que.push(make_pair(beginWord,1));
wordList1.erase(wordList1.find(beginWord));
while(!que.empty()){
pair<string,int>tmp=que.front();que.pop();
if(tmp.first==endWord)return tmp.second;
for(int i=0;i<tmp.first.size();i++)
{
string str=tmp.first;
for(int j=0;j<26;j++){
str[i]='a'+j;
if(wordList1.count(str)==1){que.push(make_pair(str,tmp.second+1));wordList1.erase(str);}
}
}
}
return 0;
}
126. Word Ladder II
该题类似,只不过要记录最短路径。
这样最后的时候我们就从终点开始一个个往前找其父结点, 直到找到起始点, 然后翻转一下加入结果集合中即可.
大概过程差不多, 但是有点不同的是当我们将字典中的一个字符串删除的时候在另一条路径上可能还会用到这个字符. 也就是像这样:
A -> C -> D, B->C->D
他们都会经过C, 并且两个都是最短的路径, 在A的时候搜索到C, 并且将C从字典中删除, 当B在搜索与其距离为1的字符串时, C已经不在字典中了, 那么怎么办呢? 我们设置一个hash表用来存储一个字符串的父结点集合, 这样C不在字典中再去查hash表看C是否在hash表中, 如果在的话并且C的父结点层次和B一样, 那么就将B也加入到C的父结点结合中去. 可以知道,一个字符串的父结点集合的距离起点的距离必然是相等的, 也就是说他们都是最短距离.
最后遍历完所有的点之后, 再用DFS从终点往前找出所有集合即可.
vector< vector<string> >res;
map< string,set<pair<string,int> > >hash;//set对应父串集合
queue< pair< string,int> >que;
void DFS(set< pair<string,int> > st,vector<string> vec){
set< pair<string,int> >::iterator it;
for(it=st.begin();it!=st.end();it++){
vec.push_back(it->first);
if(hash.count(it->first)==0){reverse(vec.begin(),vec.end());return res.push_back(vec);}
DFS(hash[it->first],vec);
vec.pop_back();
}
}
vector< vector<string> > findLadders(string beginWord,string endWord,vector<string>& wordList){
set<string>wordList1(wordList.begin(),wordList.end());
wordList1.insert(beginWord);
que.push(make_pair(beginWord,1));
wordList1.erase(wordList1.find(beginWord));
while(!que.empty()){
pair<string,int>tmp=que.front();que.pop();
for(int i=0;i<tmp.first.size();i++){
string str=tmp.first;
for(int j=0;j<26;j++)
{
str[i]='a'+j;
if(wordList1.count(str)==1){que.push(make_pair(str,tmp.second+1));wordList1.erase(str);
hash[str].insert(tmp);
}
else if(hash.count(str)&&hash[str].begin()->second==tmp.second)hash[str].insert(tmp);//在hash表且父串同层次
}
}
}
vector<string>vec;vec.push_back(endWord);
DFS(hash[endWord],vec);
return res;}