LeetCode127. 单词接龙

为什么要用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;
    }
}

 

优化过程参考:

  算法实现和优化(Java 双向 BFS,23ms)

原文地址:https://www.cnblogs.com/HuangYJ/p/14161668.html