剑指 Offer II 108. 单词演变
在字典(单词列表) wordList 中,从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列:
序列中第一个单词是 beginWord 。
序列中最后一个单词是 endWord 。
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典 wordList 中的单词。
给定两个长度相同但内容不同的单词 beginWord 和 endWord 和一个字典 wordList ,找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。
示例 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” 不在字典中,所以无法进行转换。
提示:
1 <= beginWord.length <= 10
endWord.length == beginWord.length
1 <= wordList.length <= 5000
wordList[i].length == beginWord.length
beginWord、endWord 和 wordList[i] 由小写英文字母组成
beginWord != endWord
wordList 中的所有字符串 互不相同
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/om3reC
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
分析
将本题转变为图的题:数组中每个单词代表图的节点,每个节点的邻接点是该节点更换了一个字母的其他单词,转换完成后,那么本题演变为求图的最短路径问题,用 bfs 求解。
方法一:单向 bfs
为了求两个节点之间的最短距离,用两个队列实现 bfs,一个队列 q1 中存放离起始节点距离为 d 的节点,当从这个队列中取出节点并访问时,与队列 q1 中节点相邻的节点离起始节点的距离都是 d + 1,将这些相邻的节点存放到另一个队列 q2 中。当 q1 中的节点都访问完的时候,再访问队列 q2 的节点,并将相邻的节点放入 q1 中,可以交替使用 q1 和 q2 由近及远地从起始节点开始搜索所有节点。
获取邻接点的方式用每次更换一个字母的方法存储所有可能的单词。
方法二:双向 bfs
上面的方法是按照从起始点出发不断朝着目标节点的方向搜索,直到到达目标节点。针对这类问题有种常见的优化方法,即在从起始节点出发不断朝着目标节点的方向搜索的同时,也从目标节点出发不断朝着起始节点的方向搜索。这种双向搜索的方法能够缩小搜索空间,从而提高搜索的时间效率。
对于上述的两个方向上当前要访问的节点用两个哈希表 set1 和 set2 存储,再创建一个哈希表 set3 来存储与当前访问的节点相邻的节点,用哈希表而不用队列的原因是,要判断从一个方向搜索到的节点在另一个方向是否已经访问过,只需要O(1)的时间就能判断 HashSet 种是否包含一个元素。
题解(Java)
方法一:单向 bfs
class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
Queue<String> q1 = new LinkedList<>();
Queue<String> q2 = new LinkedList<>();
Set<String> notVisited = new HashSet<>(wordList);
int length = 1;
q1.offer(beginWord);
while (!q1.isEmpty()) {
String word = q1.poll();
if (word.equals(endWord)) {
return length;
}
List<String> neighbors = getNeighbors(word);
for (String neighbor : neighbors) {
if (notVisited.contains(neighbor)) {
q2.offer(neighbor);
notVisited.remove(neighbor);
}
}
if (q1.isEmpty()) {
length++;
q1 = q2;
q2 = new LinkedList<>();
}
}
return 0;
}
//存储所有相邻的单词
private List<String> getNeighbors(String word) {
List<String> neighbors = new LinkedList<>();
char[] chars = word.toCharArray();
for (int i = 0; i < chars.length; i++) {
char old = chars[i];
for (char ch = 'a'; ch <= 'z'; ch++) {
if (old != ch) {
chars[i] = ch;
neighbors.add(new String(chars));
}
}
chars[i] = old;
}
return neighbors;
}
}
方法二:双向 bfs
class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
Set<String> notVisited = new HashSet<>(wordList);
if (!notVisited.contains(endWord)) return 0;
Set<String> set1 = new HashSet<>();
Set<String> set2 = new HashSet<>();
set1.add(beginWord);
set2.add(endWord);
int length = 2;
while (!set1.isEmpty() && !set2.isEmpty()) {
if (set2.size() <set1.size()) { //从访问节点少的方向走
Set<String> temp = set1;
set1 = set2;
set2 = temp;
}
Set<String> set3 = new HashSet<>(); //存储与当前访问的节点相邻的节点
for (String word : set1) {
List<String> neighbors = getNeighbors(word);
for (String neighbor : neighbors) {
if (set2.contains(neighbor)) {
return length;
}
if (notVisited.contains(neighbor)) {
set3.add(neighbor);
notVisited.remove(neighbor);
}
}
}
length++;
set1 = set3;
}
return 0;
}
private List<String> getNeighbors(String word) {
List<String> neighbors = new LinkedList<>();
char[] chars = word.toCharArray();
for (int i = 0; i < chars.length; i++) {
char old = chars[i];
for (char ch = 'a'; ch <= 'z'; ch++) {
if (old != ch) {
chars[i] = ch;
neighbors.add(new String(chars));
}
}
chars[i] = old;
}
return neighbors;
}
}