Breadth First Search(BFS)
下面是一个简单的双向连通图,从A一直要走到L,请问最短可以走多少路
双向扩散,即从A向右扩散,也从L向左边扩散,当扩散的两层结点有重合的时候,第一次重合的地方就是
A和L两者之间的最短路径,把左边扩散的步数加上右边扩散的步数,和就是最后 的总的步数
给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回 0。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 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)
利用广度优先搜索搜索从 beginWord 到 endWord 的路径。
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
// 查询一个单词是否在List里面存在,时间复杂度是O(n),如果是HashSet,时间复杂度就是O(1) 的
Set<String> wordList = new HashSet<String>(wordList);
// beginSet是从beginWord向外扩散,endSet是从endSet向外扩散
Set<String> beginSet = new HashSet<String>(), endSet = new HashSet<String>();
int len = 1;
int strLen = beginWord.length();
// visited指的是BFS的结点是否已经被访问过,向外面扩散到中间的时候,这个结点不能让它再往回走
HashSet<String> visited = new HashSet<String>();
beginSet.add(beginWord);
endSet.add(endWord);
// BFS starts here 这里有两个queue都应该是不为空的说明搜索已经搜索完了
// 每次扩散的时候只扩散其中一个set,优先选择小的set,如果beginSet更小就先扩散beginSet
while (!beginSet.isEmpty() && !endSet.isEmpty()) {
// 但是beginSet 大于endSet,就把beginSet和endSet对调
if (beginSet.size() > endSet.size()) {
Set<String> set = beginSet;
beginSet = endSet;
endSet = set;
}
// 这个temp表示接下来要扩散beginSet
Set<String> temp = new HashSet<String>();
// 扩散:循环queue(set跟queue一样)里面每一个单词,对每个单词变换其中一个字符位
// 针对每一个字符位的位置把它变换成abcdefg一直到z里面的任何一个字符,将字符重新替换
// 替换掉之后就得到一个新的word命名为target,如果target在endSet里面已经有了,说明找到了
// 因为endSet是从endWord那边扩散过来的,如果我们生成了target也在endSet里面,说明
// 从头扩散出来的set和从尾巴扩散出来的set 相交了,没有在endSet中的话,就会继续扩散出其他的
// target且把target加到下一次要扩散的set里面去,同时把visited标记下
// 另外在这里还需要把这个单词本身还原,还原之后,将temp给了beginSet,
// 然后length++,说明又扩散了一步
for (String word: beginSet) {
char[] chs = word.toCharArray();
for (int i = 0; i < chs.length; i++) {
for (char c = 'a'; c <= 'z'; c++) {
char old = chs[i];
chs[i] = c;
String target = String.valueOf(chs);
if (endSet.contains(target)) {
return len + 1;
}
if (!visited.contains(target) && wordList.contains(target)) {
temp.add(target);
visited.add(target);
}
chs[i] = old;
}
}
}
beginSet = temp;
len++;
}
}
class Solution(object):
def ladderLength(self, beginWord, endWord, wordList):
# endWord不在list里面肯定最后都找不到就不用找
if endWord not in wordList: return 0
front = {beginWord}
back = {endWord}
dist = 1 #走的步数
wordList = set(wordList) #定义为set方便查找
word_len = len(beginWord)
# BFS starts here
while front:
dist += 1 #每次走之前先dist加1
next_front = set() #后面扩散出来的点就放在这里面
for word in front: #将front里面的单词遍历一次
for i in range(word_len):#遍历单词的每一个字符位
for c in string.lowercase:# 将字符位取出来之后 从 'a'遍历到 -- 'z'
if c != word[i]: #将i个位置换成新的字符c,i之前的字符不变,i+1和i+1之后的这些字符也不变,只变i下标的位置
new_word = word[:i]+c+word[i+1:]
if new_word in back:#如果new_word在back中说明相交了,因为从front中扩散的new_word,在back中可以找到
return dist #返回
#不然的话就要进行扩散, 判断new_word是否在wordList中,
#如果不在说明扩散出来的new_word不是一个合法的,在单词列表里没有就不考虑
# 如果在的话,就加到next_Front中,同时把new_word从wordlist中撤掉
if new_word in wordList:
next_Front.add(new_word)
wordList.remove(new_word)
# 一开始next_Front是空,逐渐把下一次要扩散的单词都纷纷放到next_Front里面,当把front里面全部处理完
#说明next_Front已经准备好了,将next_Front给了新的front
front = next_front
if len(back) < len(front): #比较front和back之间的长度,如果back长度更小的时候,互调位置
front, back = back, front
return 0
双向BFS和单向BFS异曲同工,都是检测queue是否为空,但也有不同,不同的地方是不用queue
而是用set来表示,好处是在判断新生成的new_word是不是在另一个哥们扩散出来的set里面比较方便
比如在front里面扩散出来的点看是否在back中直接用set判断更加方便,相撞就直接返回
其他的单向和双向BFS基本一样,还有一个地方就是back和front会进行互换,不然的话
就要在两个中取舍,看哪个小就处理谁,那还不如互换下,每次只处理front,其实是一样的.