阿犇

记录生活中的点点滴滴

0%

单词接龙

127 单词接龙

传送门:https://leetcode-cn.com/problems/minimum-absolute-difference-queries/

Problem

字典 wordList 中从单词 beginWordendWord转换序列 是一个按下述规格形成的序列:

  • 序列中第一个单词是 beginWord
  • 序列中最后一个单词是 endWord
  • 每次转换只能改变一个字母。
  • 转换过程中的中间单词必须是字典 wordList 中的单词。

给你两个单词 beginWordendWord 和一个字典 wordList ,找到从 beginWordendWord最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。

示例 1

1
2
3
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:5
解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。

示例 2

1
2
3
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
输出:0
解释:endWord "cog" 不在字典中,所以无法进行转换。

提示

1
2
3
4
5
6
7
1 <= beginWord.length <= 10
endWord.length == beginWord.length
1 <= wordList.length <= 5000
wordList[i].length == beginWord.length
beginWord、endWord 和 wordList[i] 由小写英文字母组成
beginWord != endWord
wordList 中的所有字符串互不相同

bfs

可以看作最短路问题,这里的每个单词就是一种状态,可以看作一个点,如何两个单词只相差一个字母,那么这两个字母之间就存在一条边,求两个点之间的最短路。

首先,如果$endWord$不在$wordList$中,不可能存在一种转换序列,返回0;

标记$wordList$中单词是否访问可以用$map$,每次扩展考虑当前单词的每一位,每一位有25种替换方法,又1 <= beginWord.length <= 10,第一层就有25*10=250种,第二层有250*250=62500种,第三层…,但最多只有5000*250=1250000

直到扩展到$endWord$,返回步数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Solution {
public:
// bfs
string s,e;
unordered_map<string,bool> ump;
int bfs(){
queue<string> q;
unordered_map<string,int> d;
q.push(s);
d[s]=0;
while(!q.empty()){
string t=q.front();
q.pop();
if(t==e) return d[t];
int dis=d[t];
for(int i=0;i<t.size();i++){
for(char j='a';j<='z';j++){
if(j==t[i]) continue;
string tt=t.substr(0,i)+j+t.substr(i+1,t.size()-1-i);
// cout<<t<<" "<<tt<<" ";
if(ump[tt]){
if(d.count(tt)==0){
d[tt]=dis+1;
q.push(tt);
}
}
//cout<<endl;
}
}
}
return -1;
}

int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
for(auto i:wordList){
ump[i]=true;
}
s=beginWord;
e=endWord;
if(!ump[endWord]) return 0;
if(bfs()==-1) return 0;
return bfs()+1;
}
};

双向bfs

为了解决$bfs$搜索空间爆炸的问题,加速查找,可以用双向$bfs$,从$beginWord$和$endWord$分别查找。

两个方向分别$bfs$,每次选择状态数量较少的队列来扩展,可以使两个队列元素数量分布平均,也为了加快遍历的速度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class Solution {
public:
// 双向bfs,解决空间爆炸问题
unordered_map<string,bool> ump;
// beginWord和endWord
string s,e;
int bfs(){
// queue:bfs
queue<string> q1,q2;
// 标记步数,防止某一状态被重复访问
// d1[s]=k:由beginWord转换到s需要k次
// d2[s]=k:由endWord转换到s需要k次
unordered_map<string,int> d1,d2;
q1.push(s);
d1[s]=0;
q2.push(e);
d2[e]=0;
// 如果有一个queue为空,说明这一端搜到底都搜不到endWord/beginWord
// 只有两个queue都为空,才有必要继续搜索
while(!q1.empty()&&!q2.empty()){
// 不是双向各走一步,是每次选择一个方向走一步(入队)
// 为了使两个队列元素数量分布平均,也为了加快遍历的速度,选择元素较少的队列
if(q1.size()<=q2.size()){
string t=q1.front();
q1.pop();
if(d2.count(t)!=0){
return d1[t]+d2[t];
}
for(int i=0;i<t.size();i++){
for(char j='a';j<='z';j++){
if(j!=t[i]){
string tt=t.substr(0,i)+j+t.substr(i+1,t.size()-1-i);
if(ump[tt]){
if(d1.count(tt)==0){
q1.push(tt);
d1[tt]=d1[t]+1;
}
}
}
}
}
}
else{
string t=q2.front();
q2.pop();
if(d1.count(t)!=0){
return d1[t]+d2[t];
}
for(int i=0;i<t.size();i++){
for(char j='a';j<='z';j++){
if(j!=t[i]){
string tt=t.substr(0,i)+j+t.substr(i+1,t.size()-1-i);
if(ump[tt]){
if(d2.count(tt)==0){
q2.push(tt);
d2[tt]=d2[t]+1;
}
}
}
}
}
}
}
return -1;
}
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
for(auto i: wordList){
ump[i]=true;
}
if(!ump[endWord]) return 0;
s=beginWord;
e=endWord;
return bfs()+1;
}
};

计算双向$bfs$的时间复杂度:

记$wordList$长度为$n$,字符串长度为$m$,搜索的结果都会在$wordList$中出现过(不符合条件的剪枝,不入队),加上起点共$n+1$个,

最坏情况所有结点两两连通,$n$个结点进入队列,每个结点$n$条出边,时间复杂度$O(n^2)$,每次找出边时,时间复杂度$O(m)$,总时间复杂度$O(m*n^2)$。

您的支持是我继续创作的最大动力!