【双向BFS】LeetCode 127. 单词接龙

题目链接

127. 单词接龙

思路

参考宫水三叶大佬的题解

本题最朴素的解法是运用 BFS 求解,从 beginWord 出发,枚举所有替换一个字符的方案,如果方案存在于 wordList 中,则加入队列中,这样队列中就存在所有替换次数为 1 的单词。然后从队列中取出元素,继续这个过程,直到遇到 endWord 或者队列为空为止。

同时为了「防止重复枚举到某个中间结果」和「记录每个中间结果是经过多少次转换而来」,我们需要建立一个「哈希表」进行记录。哈希表的 KV 形式为 { 单词 : 由多少次转换得到 }。当枚举到新单词 str 时,需要先检查是否已经存在与「哈希表」中,如果不存在则更新「哈希表」并将新单词放入队列中。

这样的做法可以确保「枚举到所有由 beginWord 到 endWord 的转换路径」,并且由 beginWord 到 endWord 的「最短转换路径」必然会最先被枚举到。

但是这个解法的搜索空间十分巨大,所以可以采用双向BFS的方法求解,即从 beginWord 和 endWord 同时进行 BFS 搜索

image

「双向 BFS」的基本实现思路如下:

  • 创建「两个队列」分别用于两个方向的搜索;
  • 创建「两个哈希表」用于「解决相同节点重复搜索」和「记录转换次数」;
  • 为了尽可能让两个搜索方向“平均”,每次从队列中取值进行扩展时,先判断哪个队列容量较少;
  • 如果在搜索过程中「搜索到对方搜索过的节点」,说明找到了最短路径。

代码

class Solution {
    String beginWord, endWord;
    Set<String> set = new HashSet<>();

    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        this.beginWord = beginWord;
        this.endWord = endWord;
        // 将所有 word 存入 set,如果目标单词不在 set 中,说明无解
        set.addAll(wordList);
        if(!set.contains(this.endWord)){
            return 0;
        }

        return bfs() + 1;
    }

    int bfs() {
        // d1 代表从起点 beginWord 开始搜索(正向)
        // d2 代表从结尾 endWord 开始搜索(反向)
        Deque<String> d1 = new ArrayDeque<>(), d2 = new ArrayDeque();

//      m1 和 m2 分别记录两个方向出现的单词是经过多少次转换而来
//      e.g.
//      m1 = {"abc":1} 代表 abc 由 beginWord 替换 1 次字符而来
//      m2 = {"xyz":3} 代表 xyz 由 endWord 替换 3 次字符而来
        Map<String, Integer> m1 = new HashMap<>(), m2 = new HashMap<>();
        d1.add(beginWord);
        m1.put(beginWord, 0);
        d2.add(endWord);
        m2.put(endWord, 0);


//        只有两个队列都不空,才有必要继续往下搜索
//        如果其中一个队列空了,说明从某个方向搜到底都搜不到该方向的目标节点
//        e.g.
//        如果 d1 为空了,说明从 beginWord 搜索到底都搜索不到 endWord,反向搜索也没必要进行了

        while(!d1.isEmpty() && !d2.isEmpty()){
            int t = -1;
            // 为了让两个方向的搜索尽可能平均,优先拓展队列内元素少的方向
            if(d1.size() <= d2.size()){
                t = update(d1, m1, m2);
            }else{
                t = update(d2, m2, m1);
            }
            if(t != -1){
                return t;
            }
        }

        return -1;
    }

    // update 代表从 deque 中取出一个单词进行扩展,
    // current 为当前方向的距离字典;other 为另外一个方向的距离字典
    int update(Deque<String> deque, Map<String, Integer> current, Map<String, Integer> other) {
        int m = deque.size();
        while(m-- > 0){
            // 获取当前需要扩展的原字符串
            String string = deque.pollFirst();
            int n = string.length();

            // 枚举替换原字符串的哪个字符 i
            for(int i = 0; i < n; i++){
                // 枚举将 i 替换成哪个小写字母
                for(int j = 0; j < 26; j++){
                    // 替换后的字符串
                    String nextString = string.substring(0, i) + String.valueOf((char) ('a' + j)) +
                            string.substring(i + 1);
                    if(set.contains(nextString)){
                        // 如果该字符串在「当前方向」被记录过(拓展过),跳过即可
                        if(current.containsKey(nextString) && current.get(nextString) <= current.get(string) + 1){
                            continue;
                        }

                        // 如果该字符串在「另一方向」出现过,说明找到了联通两个方向的最短路
                        if(other.containsKey(nextString)){
                            return current.get(string) + 1 + other.get(nextString);
                        }else{
                            // 否则加入 deque 队列
                            deque.addLast(nextString);
                            current.put(nextString, current.get(string) + 1);
                        }
                    }
                }
            }
        }

        return -1;
    }
}

拓展

【双向BFS】LeetCode 752. 打开转盘锁

posted @ 2023-01-13 21:45  Frodo1124  阅读(39)  评论(0编辑  收藏  举报