为什么要用BFS?
无向图求最短路,广搜最为合适,广搜只要搜到了终点,那么一定是最短的路径。因为广搜就是以起点中心向四周扩散的搜索。
本题是一个无向图,需要用标记位,标记着节点是否走过,否则就会死循环!
☆☆☆方法1:BFS
☆☆☆☆方法2:双向BFS,减少搜索空间大小。
BFS的搜索空间大小依赖于每层节点的分支数量。每次都从中间结果少的那一端出发,这样就能剪枝掉很多不必要的搜索过程。
注意:如果遇到了另一个visited里有过的结点,那么说明可以连接。
关于单词转换判断
思路1:遍历所有候选单词,判断当前单词是否可以转换成这个候选单词。
思路2:因为单词是由 a~z 这有限数量的字符组成的,可以遍历当前单词能转换成的所有单词,判断其是否包含在候选单词中。候选单词用 HashSet 保存,可以大大提高判断包含关系的性能。
代码1.1:BFS(耗时523ms)
class Solution { public int ladderLength(String beginWord, String endWord, List<String> wordList) { if (!wordList.contains(endWord)) { return 0; } Queue<String> queue = new LinkedList<>(); boolean[] visited = new boolean[wordList.size()]; queue.offer(beginWord); int idx = wordList.indexOf(beginWord); if (idx != -1) { visited[idx] = true; } int level = 1; // 初始单词也算一次 while (!queue.isEmpty()) { int size = queue.size(); level ++; for (int i = 0; i < size; i++) { String cur = queue.poll(); for (int j = 0; j < wordList.size(); j++) { if (visited[j]) continue; String s = wordList.get(j); if (canConvert(cur,s)) { if (s.equals(endWord)) { return level; } queue.offer(s); visited[j] = true; } } } } return 0; } private boolean canConvert(String cur, String s) { int diff = 0; for (int i = 0; i < cur.length(); i++) { if (cur.charAt(i) != s.charAt(i)) { diff ++; if (diff > 1) break; } } return diff == 1; } }
代码1.2:BFS + 优化单词转换判断(耗时76ms)
class Solution { public int ladderLength(String beginWord, String endWord, List<String> wordList) { // 把字典中的单词放入到set中,主要是为了方便查询 HashSet<String> dictSet = new HashSet<>(wordList); if (dictSet.size() == 0 || !dictSet.contains(endWord)) { return 0; } dictSet.remove(beginWord); Queue<String> queue = new LinkedList<>(); HashSet<String> visited = new HashSet<>(); queue.offer(beginWord); visited.add(beginWord); int level = 1; while (!queue.isEmpty()) { int size = queue.size(); level ++; for (int i = 0; i < size; i++) { String cur = queue.poll(); char[] chars = cur.toCharArray(); //修改单词的每一个字符,成为新的单词 for (int j = 0; j < cur.length(); j++) { // 先保存再恢复 char temp = chars[j]; for (char k = 'a'; k <= 'z'; k++) { if (k == temp) continue; chars[j] = k; String newWord = String.valueOf(chars); if (dictSet.contains(newWord) && !visited.contains(newWord)) { if (newWord.equals(endWord)) { return level; } queue.offer(newWord); visited.add(newWord); } } chars[j] = temp; } } } return 0; } }
代码2.1:双向BFS(耗时128ms)
class Solution { public int ladderLength(String beginWord, String endWord, List<String> wordList) { int end = wordList.indexOf(endWord); if (end == -1) { return 0; } wordList.add(beginWord); int start = wordList.size() - 1; Queue<Integer> queue1 = new LinkedList<>(); Queue<Integer> queue2 = new LinkedList<>(); Set<Integer> visited1 = new HashSet<>(); Set<Integer> visited2 = new HashSet<>(); queue1.offer(start); queue2.offer(end); visited1.add(start); visited2.add(end); int level = 1; while (!queue1.isEmpty() && !queue2.isEmpty()) { if (queue1.size() > queue2.size()) { Queue<Integer> tempQueue = queue1; queue1 = queue2; queue2 = tempQueue; Set<Integer> tempSet = visited1; visited1 = visited2; visited2 = tempSet; } int size1 = queue1.size(); level ++; for (int i = 0; i < size1; i++) { String cur = wordList.get(queue1.poll()); for (int j = 0; j < wordList.size(); j++) { if (visited1.contains(j)) continue; String s = wordList.get(j); if (canConvert(cur, s)) { if (visited2.contains(j)) { // 相遇,说明可以连接 return level; } queue1.offer(j); visited1.add(j); } } } } return 0; } private boolean canConvert(String cur, String s) { int diff = 0; for (int i = 0; i < cur.length(); i++) { if (cur.charAt(i) != s.charAt(i)) { diff ++; if (diff > 1) break; } } return diff == 1; } }
代码2.2:双向BFS + 优化单词转换判断(耗时14ms)
class Solution { public int ladderLength(String beginWord, String endWord, List<String> wordList) { Set<String> dictSet = new HashSet<>(wordList); if (dictSet.size() == 0 || !dictSet.contains(endWord)) return 0; Queue<String> queue1 = new LinkedList<>(); Queue<String> queue2 = new LinkedList<>(); Set<String> visited1 = new HashSet<>(); Set<String> visited2 = new HashSet<>(); queue1.offer(beginWord); visited1.add(beginWord); queue2.offer(endWord); visited2.add(endWord); int level = 1; while (!queue1.isEmpty() && !queue2.isEmpty()) { if (queue1.size() > queue2.size()) { Queue<String> tempQueue = queue1; queue1 = queue2; queue2 = tempQueue; Set<String> tempSet = visited1; visited1 = visited2; visited2 = tempSet; } int size1 = queue1.size(); level ++; for (int i = 0; i < size1; i++) { String cur = queue1.poll(); char[] chars = cur.toCharArray(); for (int j = 0; j < cur.length(); j++) { char temp = chars[j]; for (char k = 'a'; k <= 'z'; k++) { if (k == temp) continue; chars[j] = k; String newWord = String.valueOf(chars); if (dictSet.contains(newWord) && !visited1.contains(newWord)) { if (visited2.contains(newWord)) { // 两端遍历相遇 return level; } queue1.offer(newWord); visited1.add(newWord); } } chars[j] = temp; // 恢复第j位的原始字符 } } } return 0; } }
优化过程参考: