题目描述(困难难度)

给定两个单词,一个作为开始,一个作为结束,还有一个单词列表。然后依次选择单词,只有当前单词到下一个单词只有一个字母不同才能被选择,然后新的单词再作为当前单词,直到选到结束的单词。输出这个的最短路径,如果有多组,则都输出。

思路分析

结合了开始自己的想法,又看了 Discuss,这道题有些难讲清楚,一个原因就是解法的代码会很长,这里理一下整个的思路。

如果我们从开始的单词,把与之能够转换的单词连起来,它就会长成下边的样子。

橙色表示结束单词,上图橙色的路线就是我们要找的最短路径。所以我们要做的其实就是遍历上边的树,然后判断当前节点是不是结束单词,找到结束单词后,还要判断当前是不是最短的路径。说到遍历当然就是两种思路了, 或者 BFS

解法一 DFS

利用回溯的思想,做一个 DFS。

首先要解决的问题是怎么找到节点的所有孩子节点。这里有两种方案。

第一种,遍历 wordList 来判断每个单词和当前单词是否只有一个字母不同。

这种的时间复杂度的话,如果 wordList 长度为 m,每个单词的长度为 n。那么就是 O(mn)。 第二种,将要找的节点单词的每个位置换一个字符,然后看更改后的单词在不在 wordList 中。

  1. //dict 就是 wordList,为了提高速度,从 List 转为 HashSet
  2. //cur 是我们要考虑的单词
  3. private List<String> getNext(String cur, Set<String> dict) {
  4. List<String> res = new ArrayList<>();
  5. char[] chars = cur.toCharArray();
  6. //考虑每一位
  7. for (int i = 0; i < chars.length; i++) {
  8. char old = chars[i];
  9. //考虑变成其他所有的字母
  10. for (char c = 'a'; c <= 'z'; c++) {
  11. if (c == old) {
  12. continue;
  13. }
  14. chars[i] = c;
  15. String next = new String(chars);
  16. //判断 wordList 是否包含修改后的单词
  17. if (dict.contains(next)) {
  18. res.add(next);
  19. }
  20. }
  21. chars[i] = old;
  22. }
  23. return res;
  24. }

这种的话,由于用到了 HashSet ,所以 contains 函数就是 O(1)。所以整个计算量就是 26n,所以是 O(n)

还要解决的一个问题是,因为我们要找的是最短的路径。但是事先我们并不知道最短的路径是多少,我们需要一个全局变量来保存当前找到的路径的长度。如果找到的新的路径的长度比之前的路径短,就把之前的结果清空,重新找,如果是最小的长度,就加入到结果中。

看下一递归出口。

  1. //到了结尾单词
  2. if (beginWord.equals(endWord)) {
  3. //当前长度更小,清空之前的,加新的路径加入到结果中
  4. if (min > temp.size()) {
  5. ans.clear();
  6. min = temp.size();
  7. ans.add(new ArrayList<String>(temp));
  8. //相等的话就直接加路径加入到结果中
  9. } else if (min == temp.size()) {
  10. ans.add(new ArrayList<String>(temp));
  11. }
  12. return;
  13. }
  14. //当前的长度到达了 min,还是没有到达结束单词就提前结束
  15. if (temp.size() >= min) {
  16. return;
  17. }

得到下一个节点刚才讲了两种思路,我们先采用第一种解法,看一下效果。

  1. public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
  2. List<List<String>> ans = new ArrayList<>();
  3. ArrayList<String> temp = new ArrayList<String>();
  4. //temp 用来保存当前的路径
  5. temp.add(beginWord);
  6. findLaddersHelper(beginWord, endWord, wordList, temp, ans);
  7. return ans;
  8. }
  9. int min = Integer.MAX_VALUE;
  10. private void findLaddersHelper(String beginWord, String endWord, List<String> wordList,
  11. ArrayList<String> temp, List<List<String>> ans) {
  12. if (beginWord.equals(endWord)) {
  13. if (min > temp.size()) {
  14. ans.clear();
  15. min = temp.size();
  16. ans.add(new ArrayList<String>(temp));
  17. } else if (min == temp.size()) {
  18. ans.add(new ArrayList<String>(temp));
  19. }
  20. return;
  21. }
  22. //当前的长度到达了 min,还是没有到达结束单词就提前结束
  23. if (temp.size() >= min) {
  24. return;
  25. }
  26. //遍历当前所有的单词
  27. for (int i = 0; i < wordList.size(); i++) {
  28. String curWord = wordList.get(i);
  29. //路径中已经含有当前单词,如果再把当前单词加到路径,那肯定会使得路径更长,所以跳过
  30. if (temp.contains(curWord)) {
  31. continue;
  32. }
  33. //符合只有一个单词不同,就进入递归
  34. if (oneChanged(beginWord, curWord)) {
  35. temp.add(curWord);
  36. findLaddersHelper(curWord, endWord, wordList, temp, ans);
  37. temp.remove(temp.size() - 1);
  38. }
  39. }
  40. }
  41. private boolean oneChanged(String beginWord, String curWord) {
  42. int count = 0;
  43. for (int i = 0; i < beginWord.length(); i++) {
  44. if (beginWord.charAt(i) != curWord.charAt(i)) {
  45. count++;
  46. }
  47. if (count == 2) {
  48. return false;
  49. }
  50. }
  51. return count == 1;
  52. }

但是对于普通的输入可以解决,如果 wordList 过长的话就会造成超时了。

126*. Word Ladder II - 图1

得到下一个的节点,如果采用第二种解法呢?

快了一些,但是还是超时。

我们继续来优化,首先想一下为什么会超时,看一下之前的图。

126*. Word Ladder II - 图2

DFS 的过程的话,结合上图,就是先考虑了最左边的路径,然后再回溯一下,继续到达底部。然后回溯回溯,终于到了一条含有结束单词的路径,然而事实上这条并不是最短路径。综上,我们会多判断很多无用的路径。

如果我们事先知道了最短路径长度是 4,那么我们只需要考虑前 4 层就足够了。

所以我们在 BFS 中,就把每个节点的所有相邻节点保存到 HashMap 中,就省去了 DFS 再去找相邻节点的时间。

此外,BFS 的过程中,把最短路径的高度用 min 也记录下来,在 DFS 的时候到达高度后就可以提前结束。

  1. int min = 0;
  2. public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
  3. List<List<String>> ans = new ArrayList<>();
  4. //如果不含有结束单词,直接结束,不然后边会造成死循环
  5. if (!wordList.contains(endWord)) {
  6. return ans;
  7. }
  8. //利用 BFS 得到所有的邻居节点
  9. HashMap<String, ArrayList<String>> map = bfs(beginWord, endWord, wordList);
  10. ArrayList<String> temp = new ArrayList<String>();
  11. // temp 用来保存当前的路径
  12. temp.add(beginWord);
  13. findLaddersHelper(beginWord, endWord, map, temp, ans);
  14. return ans;
  15. }
  16. private void findLaddersHelper(String beginWord, String endWord, HashMap<String, ArrayList<String>> map,
  17. ArrayList<String> temp, List<List<String>> ans) {
  18. if (beginWord.equals(endWord)) {
  19. ans.add(new ArrayList<String>(temp));
  20. return;
  21. }
  22. if(temp.size() - 1== min){
  23. return;
  24. }
  25. // 得到所有的下一个的节点
  26. ArrayList<String> neighbors = map.getOrDefault(beginWord, new ArrayList<String>());
  27. for (String neighbor : neighbors) {
  28. if (temp.contains(neighbor)) {
  29. continue;
  30. }
  31. temp.add(neighbor);
  32. findLaddersHelper(neighbor, endWord, map, temp, ans);
  33. temp.remove(temp.size() - 1);
  34. }
  35. }
  36. public HashMap<String, ArrayList<String>> bfs(String beginWord, String endWord, List<String> wordList) {
  37. Queue<String> queue = new LinkedList<>();
  38. queue.offer(beginWord);
  39. HashMap<String, ArrayList<String>> map = new HashMap<>();
  40. boolean isFound = false;
  41. Set<String> dict = new HashSet<>(wordList);
  42. while (!queue.isEmpty()) {
  43. int size = queue.size();
  44. min++;
  45. for (int j = 0; j < size; j++) {
  46. String temp = queue.poll();
  47. // 一次性得到所有的下一个的节点
  48. ArrayList<String> neighbors = getNeighbors(temp, dict);
  49. map.put(temp, neighbors);
  50. for (String neighbor : neighbors) {
  51. if (neighbor.equals(endWord)) {
  52. isFound = true;
  53. }
  54. queue.offer(neighbor);
  55. }
  56. }
  57. if (isFound) {
  58. break;
  59. }
  60. }
  61. return map;
  62. }
  63. private ArrayList<String> getNeighbors(String node, Set<String> dict) {
  64. ArrayList<String> res = new ArrayList<String>();
  65. char chs[] = node.toCharArray();
  66. for (char ch = 'a'; ch <= 'z'; ch++) {
  67. for (int i = 0; i < chs.length; i++) {
  68. if (chs[i] == ch)
  69. continue;
  70. char old_ch = chs[i];
  71. chs[i] = ch;
  72. if (dict.contains(String.valueOf(chs))) {
  73. res.add(String.valueOf(chs));
  74. }
  75. chs[i] = old_ch;
  76. }
  77. return res;
  78. }

然而这个优化,对于 的 tests 并没有什么影响。

126*. Word Ladder II - 图3

让我们继续考虑优化方案,回到之前的图。

假如我们在考虑上图中黄色节点的相邻节点,发现第三层的 abc 在第二层已经考虑过了。所以第三层的 abc 其实不用再考虑了,第三层的 abc 后边的结构一定和第二层后边的结构一样,因为我们要找最短的路径,所以如果产生了最短路径,一定是第二层的 abc 首先达到结束单词。

所以其实我们在考虑第 k 层的某一个单词,如果这个单词在第 1k-1 层已经出现过,我们其实就不过继续向下探索了。

在之前的代码中,我们其实已经考虑了部分这个问题。

  1. if (temp.contains(neighbor)) {
  2. continue;
  3. }

但我们只考虑了当前路径是否含有该单词,而就像上图表示的,其他路径之前已经考虑过了当前单词,我们也是可以跳过的。

根据这个优化思路,有两种解决方案。

第一种,再利用一个 HashMap,记为 distance 变量。在 BFS 的过程中,把第一次遇到的单词当前的层数存起来。之后遇到也不进行更新,就会是下边的效果。

126*. Word Ladder II - 图4

这样我们就可以在 DFS 的时候来判断当前黄色的节点的 distance 是不是比邻接节点的小 1。上图中 distance 都是 1 ,所以不符合,就可以跳过。

此外,在 DFS 中,因为我们每次都根据节点的层数来进行深搜,所以之前保存最短路径的全局变量 min 在这里也就不需要了。

  1. public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
  2. List<List<String>> ans = new ArrayList<>();
  3. // 如果不含有结束单词,直接结束,不然后边会造成死循环
  4. if (!wordList.contains(endWord)) {
  5. return ans;
  6. }
  7. // 利用 BFS 得到所有的邻居节点,以及每个节点的所在层数
  8. HashMap<String, Integer> distance = new HashMap<>();
  9. HashMap<String, ArrayList<String>> map = new HashMap<>();
  10. bfs(beginWord, endWord, wordList, map, distance);
  11. ArrayList<String> temp = new ArrayList<String>();
  12. // temp 用来保存当前的路径
  13. temp.add(beginWord);
  14. findLaddersHelper(beginWord, endWord, map, distance, temp, ans);
  15. return ans;
  16. }
  17. private void findLaddersHelper(String beginWord, String endWord, HashMap<String, ArrayList<String>> map,
  18. HashMap<String, Integer> distance, ArrayList<String> temp, List<List<String>> ans) {
  19. if (beginWord.equals(endWord)) {
  20. ans.add(new ArrayList<String>(temp));
  21. return;
  22. }
  23. // 得到所有的下一个的节点
  24. /*
  25. "a"
  26. "c"
  27. ["a","b","c"]*/
  28. //之所以是 map.getOrDefault 而不是 get,就是上边的情况 get 会出错
  29. ArrayList<String> neighbors = map.getOrDefault(beginWord, new ArrayList<String>());
  30. for (String neighbor : neighbors) {
  31. //判断层数是否符合
  32. if (distance.get(beginWord) + 1 == distance.get(neighbor)) {
  33. temp.add(neighbor);
  34. findLaddersHelper(neighbor, endWord, map, distance, temp, ans);
  35. temp.remove(temp.size() - 1);
  36. }
  37. }
  38. }
  39. public void bfs(String beginWord, String endWord, List<String> wordList, HashMap<String, ArrayList<String>> map,
  40. HashMap<String, Integer> distance) {
  41. Queue<String> queue = new LinkedList<>();
  42. queue.offer(beginWord);
  43. distance.put(beginWord, 0);
  44. boolean isFound = false;
  45. int depth = 0;
  46. Set<String> dict = new HashSet<>(wordList);
  47. while (!queue.isEmpty()) {
  48. int size = queue.size();
  49. depth++;
  50. for (int j = 0; j < size; j++) {
  51. String temp = queue.poll();
  52. // 一次性得到所有的下一个的节点
  53. ArrayList<String> neighbors = getNeighbors(temp, dict);
  54. map.put(temp, neighbors);
  55. for (String neighbor : neighbors) {
  56. if (!distance.containsKey(neighbor)) {
  57. distance.put(neighbor, depth);
  58. if (neighbor.equals(endWord)) {
  59. isFound = true;
  60. }
  61. queue.offer(neighbor);
  62. }
  63. }
  64. }
  65. if (isFound) {
  66. break;
  67. }
  68. }
  69. }
  70. private ArrayList<String> getNeighbors(String node, Set<String> dict) {
  71. ArrayList<String> res = new ArrayList<String>();
  72. char chs[] = node.toCharArray();
  73. for (char ch = 'a'; ch <= 'z'; ch++) {
  74. for (int i = 0; i < chs.length; i++) {
  75. if (chs[i] == ch)
  76. continue;
  77. char old_ch = chs[i];
  78. chs[i] = ch;
  79. if (dict.contains(String.valueOf(chs))) {
  80. res.add(String.valueOf(chs));
  81. }
  82. chs[i] = old_ch;
  83. }
  84. }
  85. return res;
  86. }

终于,上边的算法 AC 了。上边讲到我们提前存储了 distance ,方便在 DFS 中来判断我们是否继续深搜。

这里再讲一下另一种思路,再回顾一下这个要进行优化的图。

我们就是减少了第三层的 abc 的情况的判断。我们其实可以不用 distance ,在 BFS 中,如果发现有邻接节点在之前已经出现过了,我们直接把这个邻接节点删除不去。这样的话,在 DFS 中就不用再判断了,直接取邻居节点就可以了。

判断之前是否已经处理过,可以用一个 HashSet 来把之前的节点存起来进行判断。

这里删除邻接节点需要用到一个语言特性,java 中遍历 List 过程中,不能对 List 元素进行删除。如果想边遍历边删除,可以借助迭代器。

此外我们要判断的是当前节点在之前层有没有出现过,当前层正在遍历的节点先加到 subVisited 中。

  1. public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
  2. List<List<String>> ans = new ArrayList<>();
  3. if (!wordList.contains(endWord)) {
  4. return ans;
  5. }
  6. // 利用 BFS 得到所有的邻居节点
  7. HashMap<String, ArrayList<String>> map = new HashMap<>();
  8. bfs(beginWord, endWord, wordList, map);
  9. ArrayList<String> temp = new ArrayList<String>();
  10. // temp 用来保存当前的路径
  11. temp.add(beginWord);
  12. findLaddersHelper(beginWord, endWord, map, temp, ans);
  13. return ans;
  14. }
  15. private void findLaddersHelper(String beginWord, String endWord, HashMap<String, ArrayList<String>> map,
  16. ArrayList<String> temp, List<List<String>> ans) {
  17. if (beginWord.equals(endWord)) {
  18. ans.add(new ArrayList<String>(temp));
  19. return;
  20. }
  21. // 得到所有的下一个的节点
  22. ArrayList<String> neighbors = map.getOrDefault(beginWord, new ArrayList<String>());
  23. for (String neighbor : neighbors) {
  24. temp.add(neighbor);
  25. findLaddersHelper(neighbor, endWord, map, temp, ans);
  26. temp.remove(temp.size() - 1);
  27. }
  28. }
  29. public void bfs(String beginWord, String endWord, List<String> wordList, HashMap<String, ArrayList<String>> map) {
  30. Queue<String> queue = new LinkedList<>();
  31. queue.offer(beginWord);
  32. boolean isFound = false;
  33. int depth = 0;
  34. Set<String> dict = new HashSet<>(wordList);
  35. Set<String> visited = new HashSet<>();
  36. visited.add(beginWord);
  37. while (!queue.isEmpty()) {
  38. int size = queue.size();
  39. depth++;
  40. Set<String> subVisited = new HashSet<>();
  41. for (int j = 0; j < size; j++) {
  42. String temp = queue.poll();
  43. // 一次性得到所有的下一个的节点
  44. ArrayList<String> neighbors = getNeighbors(temp, dict);
  45. Iterator<String> it = neighbors.iterator();//把元素导入迭代器
  46. while (it.hasNext()) {
  47. String neighbor = it.next();
  48. if (!visited.contains(neighbor)) {
  49. if (neighbor.equals(endWord)) {
  50. isFound = true;
  51. }
  52. queue.offer(neighbor);
  53. subVisited.add(neighbor);
  54. }else{
  55. it.remove();
  56. }
  57. }
  58. map.put(temp, neighbors);
  59. }
  60. visited.addAll(subVisited);
  61. if (isFound) {
  62. break;
  63. }
  64. }
  65. }
  66. private ArrayList<String> getNeighbors(String node, Set<String> dict) {
  67. ArrayList<String> res = new ArrayList<String>();
  68. char chs[] = node.toCharArray();
  69. for (char ch = 'a'; ch <= 'z'; ch++) {
  70. for (int i = 0; i < chs.length; i++) {
  71. if (chs[i] == ch)
  72. continue;
  73. char old_ch = chs[i];
  74. chs[i] = ch;
  75. res.add(String.valueOf(chs));
  76. chs[i] = old_ch;
  77. }
  78. }
  79. return res;
  80. }

解法二 BFS

如果理解了上边的 DFS 过程,接下来就很好讲了。上边 DFS 借助了 BFS 把所有的邻接关系保存了起来,再用 DFS 进行深度搜索。

是完全可以的,BFS 的队列就不去存储 String 了,直接去存到目前为止的路径,也就是一个 List

  1. public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
  2. List<List<String>> ans = new ArrayList<>();
  3. // 如果不含有结束单词,直接结束,不然后边会造成死循环
  4. if (!wordList.contains(endWord)) {
  5. return ans;
  6. }
  7. bfs(beginWord, endWord, wordList, ans);
  8. return ans;
  9. }
  10. public void bfs(String beginWord, String endWord, List<String> wordList, List<List<String>> ans) {
  11. Queue<List<String>> queue = new LinkedList<>();
  12. List<String> path = new ArrayList<>();
  13. path.add(beginWord);
  14. queue.offer(path);
  15. boolean isFound = false;
  16. Set<String> dict = new HashSet<>(wordList);
  17. Set<String> visited = new HashSet<>();
  18. visited.add(beginWord);
  19. while (!queue.isEmpty()) {
  20. int size = queue.size();
  21. Set<String> subVisited = new HashSet<>();
  22. for (int j = 0; j < size; j++) {
  23. List<String> p = queue.poll();
  24. //得到当前路径的末尾单词
  25. String temp = p.get(p.size() - 1);
  26. // 一次性得到所有的下一个的节点
  27. ArrayList<String> neighbors = getNeighbors(temp, dict);
  28. for (String neighbor : neighbors) {
  29. //只考虑之前没有出现过的单词
  30. if (!visited.contains(neighbor)) {
  31. //到达结束单词
  32. if (neighbor.equals(endWord)) {
  33. isFound = true;
  34. p.add(neighbor);
  35. ans.add(new ArrayList<String>(p));
  36. p.remove(p.size() - 1);
  37. }
  38. //加入当前单词
  39. p.add(neighbor);
  40. queue.offer(new ArrayList<String>(p));
  41. p.remove(p.size() - 1);
  42. subVisited.add(neighbor);
  43. }
  44. }
  45. }
  46. visited.addAll(subVisited);
  47. if (isFound) {
  48. break;
  49. }
  50. }
  51. }
  52. private ArrayList<String> getNeighbors(String node, Set<String> dict) {
  53. ArrayList<String> res = new ArrayList<String>();
  54. char chs[] = node.toCharArray();
  55. for (char ch = 'a'; ch <= 'z'; ch++) {
  56. for (int i = 0; i < chs.length; i++) {
  57. if (chs[i] == ch)
  58. continue;
  59. char old_ch = chs[i];
  60. chs[i] = ch;
  61. if (dict.contains(String.valueOf(chs))) {
  62. res.add(String.valueOf(chs));
  63. }
  64. chs[i] = old_ch;
  65. }
  66. }
  67. return res;
  68. }

代码看起来简洁了很多。

解法三 DFS + BFS 双向搜索(two-end BFS)

在解法一的思路上,我们还能够继续优化。

解法一中,我们利用了 BFS 建立了每个节点的邻居节点。在之前的示意图中,我们把同一个字符串也画在了不同节点。这里把同一个节点画在一起,再看一下。

126*. Word Ladder II - 图5

我们可以从结束单词反向进行 BFS

这样的话,当两个方向产生了共同的节点,就是我们的最短路径了。

至于每次从哪个方向扩展,我们可以每次选择需要扩展的节点数少的方向进行扩展。

例如上图中,一开始需要向下扩展的个数是 1 个,需要向上扩展的个数是 1 个。个数相等,我们就向下扩展。然后需要向下扩展的个数就变成了 4 个,而需要向上扩展的个数是 1 个,所以此时我们向上扩展。接着,需要向上扩展的个数变成了 6 个,需要向下扩展的个数是 4 个,我们就向下扩展……直到相遇。

双向扩展的好处,我们粗略的估计一下时间复杂度。

假设 beginwordendword 之间的距离是 d。每个节点可以扩展出 k 个节点。

那么正常的时间复杂就是

126*. Word Ladder II - 图6

双向搜索的时间复杂度就是

  1. public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
  2. List<List<String>> ans = new ArrayList<>();
  3. if (!wordList.contains(endWord)) {
  4. return ans;
  5. }
  6. // 利用 BFS 得到所有的邻居节点
  7. HashMap<String, ArrayList<String>> map = new HashMap<>();
  8. bfs(beginWord, endWord, wordList, map);
  9. ArrayList<String> temp = new ArrayList<String>();
  10. // temp 用来保存当前的路径
  11. temp.add(beginWord);
  12. findLaddersHelper(beginWord, endWord, map, temp, ans);
  13. return ans;
  14. }
  15. private void findLaddersHelper(String beginWord, String endWord, HashMap<String, ArrayList<String>> map,
  16. ArrayList<String> temp, List<List<String>> ans) {
  17. if (beginWord.equals(endWord)) {
  18. ans.add(new ArrayList<String>(temp));
  19. return;
  20. }
  21. // 得到所有的下一个的节点
  22. ArrayList<String> neighbors = map.getOrDefault(beginWord, new ArrayList<String>());
  23. for (String neighbor : neighbors) {
  24. temp.add(neighbor);
  25. findLaddersHelper(neighbor, endWord, map, temp, ans);
  26. temp.remove(temp.size() - 1);
  27. }
  28. }
  29. //利用递归实现了双向搜索
  30. private void bfs(String beginWord, String endWord, List<String> wordList, HashMap<String, ArrayList<String>> map) {
  31. Set<String> set1 = new HashSet<String>();
  32. set1.add(beginWord);
  33. Set<String> set2 = new HashSet<String>();
  34. set2.add(endWord);
  35. Set<String> wordSet = new HashSet<String>(wordList);
  36. bfsHelper(set1, set2, wordSet, true, map);
  37. }
  38. // direction 为 true 代表向下扩展,false 代表向上扩展
  39. private boolean bfsHelper(Set<String> set1, Set<String> set2, Set<String> wordSet, boolean direction,
  40. HashMap<String, ArrayList<String>> map) {
  41. //set1 为空了,就直接结束
  42. //比如下边的例子就会造成 set1 为空
  43. /* "hot"
  44. "dog"
  45. ["hot","dog"]*/
  46. if(set1.isEmpty()){
  47. return false;
  48. }
  49. // set1 的数量多,就反向扩展
  50. if (set1.size() > set2.size()) {
  51. return bfsHelper(set2, set1, wordSet, !direction, map);
  52. }
  53. // 将已经访问过单词删除
  54. wordSet.removeAll(set1);
  55. wordSet.removeAll(set2);
  56. boolean done = false;
  57. // 保存新扩展得到的节点
  58. Set<String> set = new HashSet<String>();
  59. for (String str : set1) {
  60. //遍历每一位
  61. for (int i = 0; i < str.length(); i++) {
  62. char[] chars = str.toCharArray();
  63. // 尝试所有字母
  64. for (char ch = 'a'; ch <= 'z'; ch++) {
  65. if(chars[i] == ch){
  66. continue;
  67. }
  68. chars[i] = ch;
  69. String word = new String(chars);
  70. // 根据方向得到 map 的 key 和 val
  71. String key = direction ? str : word;
  72. String val = direction ? word : str;
  73. ArrayList<String> list = map.containsKey(key) ? map.get(key) : new ArrayList<String>();
  74. //如果相遇了就保存结果
  75. if (set2.contains(word)) {
  76. done = true;
  77. list.add(val);
  78. map.put(key, list);
  79. }
  80. //如果还没有相遇,并且新的单词在 word 中,那么就加到 set 中
  81. if (!done && wordSet.contains(word)) {
  82. set.add(word);
  83. list.add(val);
  84. map.put(key, list);
  85. }
  86. }
  87. }
  88. }
  89. //一般情况下新扩展的元素会多一些,所以我们下次反方向扩展 set2
  90. return done || bfsHelper(set2, set, wordSet, !direction, map);

最近事情比较多,这道题每天想一想,陆陆续续拖了好几天了。这道题本质上就是在正常的遍历的基础上,去将一些分支剪去,从而提高速度。至于方法的话,除了我上边介绍的实现方式,应该也会有很多其它的方式,但其实本质上是为了实现一样的东西。另外,双向搜索的方法,自己第一次遇到,网上搜了一下,看样子还是比较经典的一个算法。主要就是用于解决已知起点和终点,去求图的最短路径的问题。

windliang wechat

添加好友一起进步~

如果觉得有帮助的话,可以点击 给一个 star 哦 ^^

如果想系统的学习数据结构和算法,强烈推荐一个我之前学过的课程,可以点击 这里 查看详情