🌕🌕🌕 126. 单词接龙 II

吞佛童子2022年10月10日
  • algorithm
  • graph
大约 3 分钟

🌕🌕🌕 126. 单词接龙 II

难度: 🌕🌕🌕

问题描述

img_1.png


解法

class Solution {
    List<List<String>> res;
    public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
        // 思路:
        // 图的遍历 - 同时用到了广搜 & 深搜
        // 广搜 - 双向遍历,每次扩展边数少的一边,判断是否存在转换序列,并把每个节点的下一级可以连接节点结果保存
        // 深搜 - 求路径
        res = new ArrayList<>();
        HashSet<String> set = new HashSet<>(wordList);
        if(!set.contains(endWord)) {
            return res;
        }
        // 把起始节点添加进集合
        set.add(beginWord);
        HashMap<String, HashSet<String>> map = new HashMap<>();
        int count = bfs(beginWord, endWord, set, map);
        // printMap(map);
        // System.out.println(count);
        if(count < 0) {
            return res; // 说明不存在任何一条转换序列
        }
        // 存在,进行深搜,找到所有路径
        LinkedList<String> path = new LinkedList<>();
        path.addLast(beginWord);
        mySol(endWord, map, count, beginWord, path);
        return res;
    }

    private void mySol(String endWord, 
                        HashMap<String, HashSet<String>> map, 
                        int count, 
                        String pre, 
                        LinkedList<String> path) {
        // 递归终止条件
        if(path.size() == count) {
            if(pre.equals(endWord)) {
                res.add(new LinkedList<>(path));
            }
            return;
        }
        HashSet<String> set = map.get(pre);
        if(set != null) {
            for(String next : set) {
                path.addLast(next);
                mySol(endWord, map, count, next, path);
                path.removeLast();
            }
        }
    }

    private int bfs(String beginWord, String endWord, HashSet<String> wordList, HashMap<String, HashSet<String>> map) {
        boolean found = false;
        int total = wordList.size();
        // 创建 辅助队列 & 集合
        LinkedList<String> headQ = new LinkedList<>();
        HashSet<String> headS = new HashSet<>();
        LinkedList<String> tailQ = new LinkedList<>();
        HashSet<String> tailS = new HashSet<>();
        // 添加头尾节点
        headQ.offer(beginWord);
        headS.add(beginWord);
        tailQ.offer(endWord);
        tailS.add(endWord);
        // flag 标志,判断当前是头部遍历 还是 尾部遍历
        boolean flag = true;
        int count = 2; // 起始就已经有 2 个节点
        // 节点路径
        LinkedList<String> path = new LinkedList<>();
        path.addFirst(beginWord);
        path.addLast(endWord);
        while(!headQ.isEmpty() && !tailQ.isEmpty()) {
            // 双方均没有走到无法继续进行的地步
            if(headQ.size() > tailQ.size()) {
                // 需要切换头尾队列
                flag = !flag;
                LinkedList<String> tmpQ = headQ;
                headQ = tailQ;
                tailQ = tmpQ;
                HashSet<String> tmpS = headS;
                headS = tailS;
                tailS = tmpS;
            }
            // 保证每次都从头部开始广搜,通过 flag 判断当前的头部到底是初始的头部还是尾部
            // 既然需要往下走,那么节点数 ++
            count ++;
            int len = headQ.size();
            // 依次取出当前可以走的路径,判断是否可以和尾部已有的节点重合,如果有,说明当前是最短路径
            // 遍历当前下所有路径,把能够成功的所有路径添加到 res,返回
            // 若不存在,当前路径压入队列,继续广搜
            HashSet<String> nextSet = new HashSet<>(); // 当前层 next 节点单独存放
            for(int i = 0; i < len; i ++) {
                String cur = headQ.poll();
                // 寻找 cur 可以到达的 next 节点
                for(String next : wordList) {                  
                    if(headS.contains(next)) {
                        // 这一步找了好久的问题,
                        // 可能出现同一层前面节点没有满足条件,直接将其 next 节点加入的情况
                        // 因此需要将同一层的 next 节点统一添加到一个新的 set 中去
                        // 如果直接全部加入 nextSet 会超时,因此采用 headS 添加的同时,加入到 nextS
                        if(!nextSet.contains(next)) {
                            continue; // 当前方向已经遍历过该节点,说明不是本轮其他节点的下一个节点
                        }
                    }
                    if(!isValid(cur, next)) {
                        continue; // next 不是与 cur 匹配的 next
                    }                 
                    // 找到符合条件的 next 节点
                    // 判断是否可以和尾端匹配
                    if(tailS.contains(next)) {
                        // 更改标志位,表示已经找到满足条件的节点,下一轮就不需要了,但是当前轮还是需要继续进行
                        if(!found) {
                            found = true;
                            count --; // 说明是首个匹配的转换序列
                        }
                    } else {
                        if(!nextSet.contains(next)) { // 这里也是优化的点,保证不超时
                            nextSet.add(next);
                            headS.add(next);
                            headQ.offer(next); 
                        }
                    }
                    // System.out.println(cur + "  " + next + "  " + flag);
                    // printSet(headS);
                    // 当前没有找到任何满足条件的转换序列,填充 map
                    if(flag) {
                        // 说明是正向遍历,from == cur
                        if(!map.containsKey(cur)) {
                            HashSet<String> tmpS = new HashSet<>();
                            tmpS.add(next);
                            map.put(cur, tmpS);
                        } else {
                            HashSet<String> tmpS = map.get(cur);
                            tmpS.add(next);
                            map.put(cur, tmpS);
                        }
                    } else {
                        // 反向遍历,from == next,因为之后深搜需要按照正向方向进行
                        if(!map.containsKey(next)) {
                            HashSet<String> tmpS = new HashSet<>();
                            tmpS.add(cur);
                            map.put(next, tmpS);
                        } else {
                            HashSet<String> tmpS = map.get(next);
                            tmpS.add(cur);
                            map.put(next, tmpS);
                        }
                    }
                }
            }  
            if(found) {
                return count;
            }          
        }     
        return -1; // 说明没有一条转换序列 
    }

    private boolean isValid(String a, String b) {
        int len = a.length();
        int diff = 0;
        for(int i = 0; i < len; i ++) {
            if(a.charAt(i) != b.charAt(i)) {
                if(diff == 1) {
                    return false;
                } else {
                    diff ++;
                }
            }
        }   
        return diff == 1;
    }

    private void printMap(HashMap<String, HashSet<String>> map) {
        for(Map.Entry<String, HashSet<String>> entry : map.entrySet()) {
            System.out.println("key: " +  entry.getKey());
            System.out.print("value: ");
            for(String cur : entry.getValue()) {
                System.out.print(cur + "  ");
            }
            System.out.println();
            System.out.println();
        }
    }

    private void printSet(HashSet<String> set) {
        for(String cur : set) {
            System.out.print(cur + "  ");
        }
        System.out.println();
        System.out.println();
    }
}

输出

img.png

上次编辑于: 2022/10/10 下午8:43:48
贡献者: liuxianzhishou