力扣第126题-单词接龙 II

451 阅读3分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

前言

力扣第126题 单词接龙 II 如下所示:

按字典 wordList 完成从单词 beginWord 到单词 endWord 转化,一个表示此过程的 转换序列 是形式上像 beginWord -> s1 -> s2 -> ... -> sk 这样的单词序列,并满足:

  • 每对相邻的单词之间仅有单个字母不同。
  • 转换过程中的每个单词 si1 <= i <= k)必须是字典 wordList 中的单词。注意,beginWord 不必是字典 wordList 中的单词。
  • sk == endWord

给你两个单词 beginWord 和 endWord ,以及一个字典 wordList 。请你找出并返回所有从 beginWord 到 endWord 的 最短转换序列 ,如果不存在这样的转换序列,返回一个空列表。每个序列都应该以单词列表 [beginWord, s1, s2, ..., sk] 的形式返回。

示例 1:

输入: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出: [["hit","hot","dot","dog","cog"],["hit","hot","lot","log","cog"]]
解释: 存在 2 种最短的转换序列:
"hit" -> "hot" -> "dot" -> "dog" -> "cog"
"hit" -> "hot" -> "lot" -> "log" -> "cog"

image.png

一、思路

题目很长,也不是很好理解,需要多读几遍题目。简单翻译一下题目就是:endWord 中的字符替换 beginWord 中的字符,每次只替换一个字符(注意下一次替换是在 beginWord' 的基础上进行的)。直到替换的字符 beginWord' 都要在集合 wordList 中出现

我们再把题目中的重要信息列举一下(注意看提示!):

  • beginWord.len == endWord.len 两字符串长度相等
  • beginWord != endWord 两者内容不一致。()
  • 所有字符均有小写字母构成
  • wordList 中所有单词都不相同(如有重复元素肯定找不到序列)

图解算法

以下的思路根据题目中的示例一作为具体的例子分析

我们既然知道了结果集合 wordList,那么就可以推断出从 beginWord -> endWord 的路径上会有哪些节点,以及各个节点间的关联。例如 hot 可以通过字符的替换变为 dotlot。具体的关联图如下所示:

为了更好的看清整个过程,我将起始节点 beginWord 也加进来了

image.png

我们可以看到节点的关联中还存在双向关联的情况,像这种双向的关联我们可以直接删除掉,因为最终的结果中要求的是 最短路径,如果选择了双向关联的两个节点会导致路径的长度边长。

image.png

然后我们根据图中的关联关系很容以就能找到最短的路径了,两条最短的路径如下图所示:

第一条 image.png

第二条

image.png

思路有了实现起来也不难了,主要分为以下两个步骤:

  1. 使用 广度遍历 生成一个有向图,明确各个节点间的关联关系
  2. 利用关联关系,递归 找到最短的路径

二、实现

实现代码

在构建图的时候,可以从开始字符串出发,从 a ~ z 逐个替换字符,直到在 wordList 中出现

    public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
        List<List<String>> res = new ArrayList<>();
        Set<String> dict = new HashSet<>(wordList); // 字典
        if (!dict.contains(endWord)) {
            return res;
        }
        dict.remove(beginWord); // 要交换字符,字典中不能有起始字符串 beginWord

        Map<String, Integer> steps = new HashMap<>();
        steps.put(beginWord, 0);
        Map<String, Set<String>> from = new HashMap<>();
        boolean found = bfs(beginWord, endWord, dict, steps, from);

        if (found) {
            Deque<String> path = new ArrayDeque<>();
            path.add(endWord);
            dfs(from, path, beginWord, endWord, res);
        }
        return res;
    }


    /**
     * 广度遍历
     */
    private boolean bfs(String beginWord, String endWord, Set<String> dict, Map<String, Integer> steps, Map<String, Set<String>> from) {
        int wordLen = beginWord.length();
        int step = 0;
        boolean found = false;  // 是否找到了结果字符串

        Queue<String> queue = new LinkedList<>();
        queue.add(beginWord);
        while (!queue.isEmpty()) {
            step++;
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                String currWord = queue.remove();
                char[] charArray = currWord.toCharArray();
                for (int j = 0; j < wordLen; j++) {
                    char origin = charArray[j];
                    // 尝试找到下一个替换后的字符串
                    for (char c = 'a'; c <= 'z'; c++) {
                        charArray[j] = c;
                        String nextWord = String.valueOf(charArray);
                        if (steps.containsKey(nextWord) && steps.get(nextWord) == step) {
                            from.get(nextWord).add(currWord);
                        }
                        if (!dict.contains(nextWord)) {
                            continue;
                        }
                        dict.remove(nextWord);
                        queue.add(nextWord);
                        if (!from.containsKey(nextWord)){
                            from.put(nextWord, new HashSet<>());
                        }
                        from.get(nextWord).add(currWord);
                        steps.put(nextWord, step);
                        if (nextWord.equals(endWord)) {
                            found = true;
                        }
                    }
                    charArray[j] = origin;
                }
            }
            if (found) {
                break;
            }
        }
        return found;
    }

    /**
     * 递归
     */
    private void dfs(Map<String, Set<String>> from, Deque<String> path, String beginWord, String cur, List<List<String>> res) {
        if (cur.equals(beginWord)) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (String precursor : from.get(cur)) {
            path.addFirst(precursor);
            dfs(from, path, beginWord, precursor, res);
            path.removeFirst();
        }
    }

测试代码

    public static void main(String[] args) {
        String being = "hit";
        String end = "cog";
        List<String> list = Arrays.asList("hot","dot","dog","lot","log","cog");
        new Number126().findLadders(being, end, list);
    }

结果

image.png

三、总结

感谢看到最后,非常荣幸能够帮助到你~♥

如果你觉得我写的还不错的话,不妨给我点个赞吧!如有疑问,也可评论区见~