这道题很容易想到使用广度优先搜索的方式,从起始单词start搜索到结束单词end,中间产生状态的产生有两种,一种是通过改变单词的一个字母并其是否在字典中来产生新的状态;另一种是通过以距离为1借助字典构建单词间的邻接关系。第二种状态产生的方式,太天真,由于字典有可能很大,构建邻接关系图的复杂度为单词个数的平方,无法接受。我第一个版本的代码使用此种方法生成新的状态超时。
在选用通过改变单词中的一个字母来产生新的状态之后,在广度优先搜索的时候,需要记录已经搜索过的顶点,以防止重复搜索,开始时,我使用额外的visitd结合表示此单词是否被访问过,运行时间是1466ms,而直接通过在字典中删除此单词,使得判定此单词不在字典中标志此单词被访问过的方法,运行时间只有396ms,如此大的提升,为什么,现在推断的原因是由于set访问比较慢。
接着,我使用双向广度优先搜索对程序进行优化,结果运行时间下降到了105ms,相对广度优先搜索提高了2倍。
下面是3个版本的程序:
1.单向广搜,使用visited记录访问过的状态(1466ms)
int ladderLength(string start, string end, unordered_set<string> &dict) {
int n=dict.size();
if(0==n)return 0;
set<string>visited;
visited.insert(start);
queue<string>words;
words.push(start);
int level=1;
int cnt=1;
while(!words.empty()){
string word=words.front();
words.pop();
for(string::size_type i=0;i<word.size();i++){
char t=word[i];
for(char c='a';c<='z';c++){
if(t==c)continue;
string t=word;
word[i]=c;
if(word==end)return level+1;
if(!visited.count(word)&&dict.count(word)){
words.push(word);
visited.insert(word);
}
word=t;
}
}
if(--cnt==0){
level++;
cnt=words.size();
}
}
return 0;
}
2.单向广搜,使用删除单词标志此单词被访问(396ms)
int ladderLength(string start, string end, unordered_set<string> &dict) {
int n=dict.size();
if(0==n)return 0;
queue<string>words;
words.push(start);
dict.erase(start);
int level=1;
int cnt=1;
while(!words.empty()){
string word=words.front();
words.pop();
for(string::size_type i=0;i<word.size();i++){
char t=word[i];
for(char c='a';c<='z';c++){
if(t==c)continue;
string t=word;
word[i]=c;
if(word==end)return level+1;
if(dict.count(word)){
words.push(word);
dict.erase(word);
}
word=t;
}
}
if(--cnt==0){
level++;
cnt=words.size();
}
}
return 0;
}
3.双向广搜(105ms)
int ladderLength(string start, string end, unordered_set<string> &dict) {
int n=dict.size();
if(0==n)return 0;
queue<string>firstQueue;
map<string,int>firstVisited;
firstQueue.push(start);firstVisited.insert(pair<string,int>(start,1));
queue<string>secondQueue;
map<string,int>secondVisited;
secondQueue.push(end);secondVisited.insert(pair<string,int>(end,1));
int firstLevel=1,secondLevel=1;
int firstCnt=1,secondCnt=1;
dict.erase(start);dict.erase(end);
bool first=true;
while(!firstQueue.empty()||!secondQueue.empty()){
string word=first?firstQueue.front():secondQueue.front();
if(first)firstQueue.pop();
else secondQueue.pop();
for(string::size_type i=0;i<word.size();i++){
char t=word[i];
for(char c='a';c<='z';c++){
if(t==c)continue;
string t=word;
word[i]=c;
if(first&&secondVisited.count(word))return firstLevel+secondVisited[word];
if(!first&&firstVisited.count(word))return secondLevel+firstVisited[word];
if(dict.count(word)){
if(first){
firstQueue.push(word);
firstVisited.insert(pair<string,int>(word,firstLevel+1));
}
else{
secondQueue.push(word);
secondVisited.insert(pair<string,int>(word,secondLevel+1));
}
dict.erase(word);
}
word=t;
}
}
if(first){
if(--firstCnt==0){
firstLevel++;
firstCnt=firstQueue.size();
}
}
else{
if(--secondCnt==0){
secondLevel++;
secondCnt=secondQueue.size();
}
}
first=!first;
}
return 0;
}
双向广搜需要添加额外的firstVisited,secondVisited用来记录访问过的节点所在的层次,用于计算整个变换的长度。
因为在单向广搜中,搜索到最终状态,直接用当前节点的层次就能计算变换长度,而在双向广搜中,要根据当前节点
是否在另一个队列中被搜索过,以标志结束,但是此节点在另外一个队列中被搜索的路径长度,并不一定是其正在搜索
的层次,也有可能是新加入顶点的层次。