题目

类型:BFS
难度:困难
image.png

解题思路

  • 无向图中两个顶点之间的最短路径的长度,可以通过广度优先遍历得到;
  • 为什么 BFS 得到的路径最短?可以把起点和终点所在的路径拉直来看,两点之间线段最短;
  • 已知目标顶点的情况下,可以分别从起点和目标顶点(终点)执行广度优先遍历,直到遍历的部分有交集,这是双向广度优先遍历的思想。

代码

  1. import java.util.ArrayList;
  2. import java.util.Collections;
  3. import java.util.HashSet;
  4. import java.util.List;
  5. import java.util.Set;
  6. public class Solution {
  7. public int ladderLength(String beginWord, String endWord, List<String> wordList) {
  8. // 第 1 步:先将 wordList 放到哈希表里,便于判断某个单词是否在 wordList 里
  9. Set<String> wordSet = new HashSet<>(wordList);
  10. if (wordSet.size() == 0 || !wordSet.contains(endWord)) {
  11. return 0;
  12. }
  13. // 第 2 步:已经访问过的 word 添加到 visited 哈希表里
  14. Set<String> visited = new HashSet<>();
  15. // 分别用左边和右边扩散的哈希表代替单向 BFS 里的队列,它们在双向 BFS 的过程中交替使用
  16. Set<String> beginVisited = new HashSet<>();
  17. beginVisited.add(beginWord);
  18. Set<String> endVisited = new HashSet<>();
  19. endVisited.add(endWord);
  20. // 第 3 步:执行双向 BFS,左右交替扩散的步数之和为所求
  21. int step = 1;
  22. while (!beginVisited.isEmpty() && !endVisited.isEmpty()) {
  23. // 优先选择小的哈希表进行扩散,考虑到的情况更少
  24. if (beginVisited.size() > endVisited.size()) {
  25. Set<String> temp = beginVisited;
  26. beginVisited = endVisited;
  27. endVisited = temp;
  28. }
  29. // 逻辑到这里,保证 beginVisited 是相对较小的集合,nextLevelVisited 在扩散完成以后,会成为新的 beginVisited
  30. Set<String> nextLevelVisited = new HashSet<>();
  31. for (String word : beginVisited) {
  32. if (changeWordEveryOneLetter(word, endVisited, visited, wordSet, nextLevelVisited)) {
  33. return step + 1;
  34. }
  35. }
  36. // 原来的 beginVisited 废弃,从 nextLevelVisited 开始新的双向 BFS
  37. beginVisited = nextLevelVisited;
  38. step++;
  39. }
  40. return 0;
  41. }
  42. /**
  43. * 尝试对 word 修改每一个字符,看看是不是能落在 endVisited 中,扩展得到的新的 word 添加到 nextLevelVisited 里
  44. *
  45. * @param word
  46. * @param endVisited
  47. * @param visited
  48. * @param wordSet
  49. * @param nextLevelVisited
  50. * @return
  51. */
  52. private boolean changeWordEveryOneLetter(String word, Set<String> endVisited,
  53. Set<String> visited,
  54. Set<String> wordSet,
  55. Set<String> nextLevelVisited) {
  56. char[] charArray = word.toCharArray();
  57. for (int i = 0; i < word.length(); i++) {
  58. char originChar = charArray[i];
  59. for (char c = 'a'; c <= 'z'; c++) {
  60. if (originChar == c) {
  61. continue;
  62. }
  63. charArray[i] = c;
  64. String nextWord = String.valueOf(charArray);
  65. if (wordSet.contains(nextWord)) {
  66. if (endVisited.contains(nextWord)) {
  67. return true;
  68. }
  69. if (!visited.contains(nextWord)) {
  70. nextLevelVisited.add(nextWord);
  71. visited.add(nextWord);
  72. }
  73. }
  74. }
  75. // 恢复,下次再用
  76. charArray[i] = originChar;
  77. }
  78. return false;
  79. }
  80. }