127. 单词接龙

127. 单词接龙

leetcode 127. 单词接龙


题目:127. 单词接龙

给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:

  1. 每次转换只能改变一个字母。
  2. 转换过程中的中间单词必须是字典中的单词。

说明:

  • 如果不存在这样的转换序列,返回 0。
  • 所有单词具有相同的长度。
  • 所有单词只由小写字母组成。
  • 字典中不存在重复的单词。
  • 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。

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

示例1:

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

示例2:

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

方法一:BFS(广度优先搜索)

思路:BFS(广度优先搜索),将当前单词只改变一个字母的所有单词都视为可扩展搜索的方向,通过判断改变的单词是否在词库中,选择是否将其扩展入队(在词库中则入队)。直至扩展到endWord(此时说明已搜索到目标数),或者队列为空(此时说明无法搜索到目标数),则搜索结束。Set检索元素效率低下,删除和插入效率高;List查找元素效率高,插入删除元素效率低。但是若用contains方法查询对象元素,Set集合应该比List效率要高,所以将词库wordList转成Set,以提高效率。

运行数据:执行用时:64 ms,内存消耗:41.6 MB

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
76
77
78
79
// LeetCode指定调用方法
public int ladderLength(String beginWord, String endWord, List<String> wordList) {

// 记录结果
int result = 0;

// 将词库wordList转成Set,以提高效率
HashSet<String> words = new HashSet<>(wordList);

// 用于BFS的队列
Queue<String> queue = new LinkedList<String>();

// 将起点单词入队
queue.offer(beginWord);

// 记录当前队列的长度,当队列为空时,BFS全部搜索结束(用以代替 queue.isEmpty() 增加每次循环判断时的效率)
int queueLen = 1;

// BFS
while (queueLen > 0) {

// 记录当前扩展轮的单词的个数
int currentQueueLen = queueLen;

// 更新结果
result++;

words.removeAll(queue);

// 扩展当前轮次的单词
while (currentQueueLen-- > 0) {

// 将队列中第一个单词出队
String currentString = queue.poll();

// 更新当前队列长度
queueLen--;

// 将单词转换成字符数组以便使用
char[] chars = currentString.toCharArray();

// 当前单词只改变一个字母的所有单词都视为可扩展搜索的方向,以下两个循环均用于遍历可扩展单词
for (int i = 0; i < chars.length; i++) {

// 记录当前字母
char temp = chars[i];

for (char j = 'a'; j <= 'z'; j++) {

// 改变当前字母
chars[i] = j;

// 改变一个字母之后的单词
String word = new String(chars);

// 判断改变的单词是否在词库中
if (words.contains(word)) {

// 扩展到endWord,此时说明已搜索到目标单词,返回结果
if (word.equals(endWord)) {
return result + 1;
}

// 将扩展单词入队
queue.offer(word);

// 更新当前队列长度
queueLen++;
}
}

// 将以改变的字母恢复原来的值,以便下一次改变单词字母
chars[i] = temp;
}
}
}

return 0;
}

方法二:双向BFS(广度优先搜索)

思路:双向BFS(广度优先搜索),在上述基本BFS的基础上,进行该进。endWord为起点endWord为终点,用start、end分别记录从起点端向终点端的扩展单词和从终点向起点扩展的单词,一开始从起点向终点扩展,扩展一轮后如果当前的扩展单词数大于另一个方向的扩展单词数则改变方向,从终点往起点开始扩展。依次类推,根据这个规则改变搜索方向,直至当前方向的搜索搜索到另一个方向的扩展单词(此时说明已收索到目标数),或者当前搜索方向的扩展单词为空(此时说明无法搜索到目标数),则搜索结束。

运行数据:执行用时:11 ms,内存消耗:40.7 MB

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
76
77
78
79
// LeetCode指定调用方法
public int ladderLength(String beginWord, String endWord, List<String> wordList) {

// 初始化两个方向记录扩展的集合
HashSet<String> start = new HashSet<>();
start.add(beginWord);
HashSet<String> end = new HashSet<>();
end.add(endWord);

// 将词库类型转成Set
HashSet<String> words = new HashSet<>(wordList);

// 如果终点单词不在词库中,则无法到达终点,直接返回0
if (!words.contains(endWord)) {
return 0;
}

return bothwayBfs(start,end,words,2);
}

// 双向BFS
private int bothwayBfs(HashSet<String> start, HashSet<String> end, HashSet<String> words, int depth) {

// 当前的扩展单词数大于另一个方向的扩展单词数则改变方向
if (start.size() > end.size()) {
return bothwayBfs(end, start, words, depth);
}

// 清除词库中已被搜索过的单词,防止重复搜索
words.removeAll(start);

// 记录当前轮的扩展单词
HashSet<String> next = new HashSet<>();

// 对上一轮扩展的单词,进行搜索扩展
for (String str : start) {

// 将单词转换成字符数组以便使用
char[] chars = str.toCharArray();

// 当前单词只改变一个字母的所有单词都视为可扩展搜索的方向,以下两个循环均用于遍历可扩展单词
for (int i = 0; i < chars.length; i++) {

// 记录当前字母
char temp = chars[i];

for (char j = 'a'; j <= 'z'; j++) {

// 改变当前字母
chars[i] = j;

// 改变一个字母之后的单词
String word = new String(chars);

// 判断改变的单词是否在词库中
if (words.contains(word)) {

// 当前方向的搜索搜索到另一个方向的扩展单词,此时说明已收索到目标数,返回扩展次数
if (end.contains(word)) {
return depth;
}
next.add(word);
}
}

// 将以改变的字母恢复原来的值,以便下一次改变单词字母
chars[i] = temp;
}
}

// 当前搜索方向的扩展单词为空,此时说明无法搜索到目标数,直接返回0
if (start.isEmpty()) {
return 0;
}

// 进行下一轮搜索扩展
return bothwayBfs(next, end, words, depth + 1);

}

学习所得,资料、图片部分来源于网络,如有侵权,请联系本人删除。

才疏学浅,若有错误或不当之处,可批评指正,还请见谅!


 
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×