搜索题目:单词接龙

文章目录

题目

标题和出处

标题:单词接龙

出处:127. 单词接龙

难度

5 级

题目描述

要求

字典 wordList \texttt{wordList} wordList 中从开始单词 beginWord \texttt{beginWord} beginWord 到结束单词 endWord \texttt{endWord} endWord 的转换序列 是一个按下述规格形成的序列 beginWord → s 1 → s 2 → ... → s k \texttt{beginWord} \rightarrow \texttt{s}\texttt{1} \rightarrow \texttt{s}\texttt{2} \rightarrow \ldots \rightarrow \texttt{s}_\texttt{k} beginWord→s1→s2→...→sk:

  • 每一对相邻的单词只差一个字母。
  • 对于 1 ≤ i ≤ k \texttt{1} \le \texttt{i} \le \texttt{k} 1≤i≤k,每个 s i \texttt{s}_\texttt{i} si 都在 wordList \texttt{wordList} wordList 中。注意 beginWord \texttt{beginWord} beginWord 不需要在 wordList \texttt{wordList} wordList 中。
  • s k = endWord \texttt{s}_\texttt{k} = \texttt{endWord} sk=endWord

给定两个单词 beginWord \texttt{beginWord} beginWord 和 endWord \texttt{endWord} endWord,以及一个字典 wordList \texttt{wordList} wordList,返回从 beginWord \texttt{beginWord} beginWord 到 endWord \texttt{endWord} endWord 的最短转换序列 中的单词数目 。如果不存在这样的转换序列,返回 0 \texttt{0} 0。

示例

示例 1:

输入: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] \texttt{beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]} beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]

输出: 5 \texttt{5} 5

解释:一个最短转换序列是 "hit" → "hot" → "dot" → "dog" → "cog" \texttt{"hit"} \rightarrow \texttt{"hot"} \rightarrow \texttt{"dot"} \rightarrow \texttt{"dog"} \rightarrow \texttt{"cog"} "hit"→"hot"→"dot"→"dog"→"cog",长度为 5 \texttt{5} 5。

示例 2:

输入: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"] \texttt{beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]} beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]

输出: 0 \texttt{0} 0

解释:结束单词 "cog" \texttt{"cog"} "cog" 不在字典中,所以不存在有效的转换序列。

数据范围

  • 1 ≤ beginWord.length ≤ 10 \texttt{1} \le \texttt{beginWord.length} \le \texttt{10} 1≤beginWord.length≤10
  • endWord.length = beginWord.length \texttt{endWord.length} = \texttt{beginWord.length} endWord.length=beginWord.length
  • 1 ≤ wordList.length ≤ 5000 \texttt{1} \le \texttt{wordList.length} \le \texttt{5000} 1≤wordList.length≤5000
  • wordList[i].length = beginWord.length \texttt{wordList[i].length} = \texttt{beginWord.length} wordList[i].length=beginWord.length
  • beginWord \texttt{beginWord} beginWord、 endWord \texttt{endWord} endWord 和 wordList[i] \texttt{wordList[i]} wordList[i] 由小写英语字母组成
  • beginWord ≠ endWord \texttt{beginWord} \ne \texttt{endWord} beginWord=endWord
  • wordList \texttt{wordList} wordList 中的所有字符串各不相同

解法一

思路和算法

这道题要求计算从 beginWord \textit{beginWord} beginWord 到 endWord \textit{endWord} endWord 的最短转换序列中的单词数,可以使用广度优先搜索实现,广度优先搜索可以确保得到最短路径。

为了快速判断一个单词是否在字典中,需要使用哈希集合存储字典中的每个单词。只有当 endWord \textit{endWord} endWord 在字典中时,才可能存在从 beginWord \textit{beginWord} beginWord 到 endWord \textit{endWord} endWord 的转换序列,因此首先判断 endWord \textit{endWord} endWord 是否在字典中,如果 endWord \textit{endWord} endWord 不在字典中则不存在从 beginWord \textit{beginWord} beginWord 到 endWord \textit{endWord} endWord 的转换序列,返回 0 0 0。以下只考虑 endWord \textit{endWord} endWord 在字典中的情况。

规定 beginWord \textit{beginWord} beginWord 在第 1 1 1 层,从 beginWord \textit{beginWord} beginWord 开始遍历,每次遍历同一层的全部单词,并得到下一层的全部单词。遍历过程中如果遇到 endWord \textit{endWord} endWord,则 endWord \textit{endWord} endWord 所在层为最短转换序列中的单词数目。如果遍历结束仍未发现 endWord \textit{endWord} endWord,则不存在从 beginWord \textit{beginWord} beginWord 到 endWord \textit{endWord} endWord 的转换序列。

广度优先搜索的过程中,需要使用哈希集合存储已访问的单词。初始时将 beginWord \textit{beginWord} beginWord 添加到已访问的哈希集合,将 beginWord \textit{beginWord} beginWord 入队列,将层数初始化为 0 0 0。

每一轮遍历时,首先将层数加 1 1 1,并得到队列内的单词个数,此时队列内的单词为同一层的全部单词,然后访问这些单词,获得下一层的全部单词并入队列。从当前单词获得下一层的单词的做法是,分别将当前单词的每个字符替换成其他可能的字符,得到与当前单词恰好有一个字符不同的新单词,如果新单词是未访问的单词,则新单词是下一层的单词。

一轮遍历结束之后,当前层的全部单词都已经出队列并被访问,此时队列内的元素为下一层的全部单词,下一轮遍历时即可访问下一层的全部单词。该做法可以确保每一轮遍历的单词为同一层的全部单词。

由于遍历过程中维护层数,因此当遇到 endWord \textit{endWord} endWord 时,当前层即为从 beginWord \textit{beginWord} beginWord 到 endWord \textit{endWord} endWord 的最短转换序列中的单词数目,返回当前层。

如果遍历结束之后仍未遇到 endWord \textit{endWord} endWord,则不存在从 beginWord \textit{beginWord} beginWord 到 endWord \textit{endWord} endWord 的转换序列,返回 0 0 0。

代码

java 复制代码
class Solution {
    int wordLength;

    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        Set<String> wordSet = new HashSet<String>();
        for (String word : wordList) {
            wordSet.add(word);
        }
        if (!wordSet.contains(endWord)) {
            return 0;
        }
        wordLength = beginWord.length();
        Set<String> visited = new HashSet<String>();
        visited.add(beginWord);
        Queue<String> queue = new ArrayDeque<String>();
        queue.offer(beginWord);
        int wordCount = 0;
        while (!queue.isEmpty()) {
            wordCount++;
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                String word = queue.poll();
                if (word.equals(endWord)) {
                    return wordCount;
                }
                List<String> adjacentWords = getAdjacentWords(word);
                for (String adjacent : adjacentWords) {
                    if (wordSet.contains(adjacent) && visited.add(adjacent)) {
                        queue.offer(adjacent);
                    }
                }
            }
        }
        return 0;
    }

    public List<String> getAdjacentWords(String word) {
        List<String> adjacentWords = new ArrayList<String>();
        char[] arr = word.toCharArray();
        for (int i = 0; i < wordLength; i++) {
            char original = arr[i];
            for (char c = 'a'; c <= 'z'; c++) {
                if (c == original) {
                    continue;
                }
                arr[i] = c;
                adjacentWords.add(new String(arr));
            }
            arr[i] = original;
        }
        return adjacentWords;
    }
}

复杂度分析

  • 时间复杂度: O ( ∣ Σ ∣ × m × n ) O(|\Sigma| \times m \times n) O(∣Σ∣×m×n),其中 Σ \Sigma Σ 是字符集, m m m 是单词的长度, n n n 是字典的大小,这道题中 Σ \Sigma Σ 是全部小写英语字母, ∣ Σ ∣ = 26 |\Sigma| = 26 ∣Σ∣=26。广度优先搜索最多需要遍历每个单词一次,对于每个单词计算其下一层的单词的时间是 O ( ∣ Σ ∣ × m ) O(|\Sigma| \times m) O(∣Σ∣×m),因此时间复杂度是 O ( ∣ Σ ∣ × m × n ) O(|\Sigma| \times m \times n) O(∣Σ∣×m×n)。

  • 空间复杂度: O ( m × n ) O(m \times n) O(m×n),其中 m m m 是单词的长度, n n n 是字典的大小。哈希集合和队列需要 O ( m × n ) O(m \times n) O(m×n) 的空间。

解法二

思路和算法

也可以使用双向广度优先搜索计算最少变化次数。

首先判断 endWord \textit{endWord} endWord 是否在字典中,如果 endWord \textit{endWord} endWord 不在字典中则不存在从 beginWord \textit{beginWord} beginWord 到 endWord \textit{endWord} endWord 的转换序列,返回 0 0 0。以下只考虑 endWord \textit{endWord} endWord 在字典中的情况。

双向广度优先搜索分别从 beginWord \textit{beginWord} beginWord 和 endWord \textit{endWord} endWord 开始搜索,需要对两个方向的广度优先搜索分别维护一个哈希集合与一个队列。为了减少每次遍历的单词数,每一轮遍历时首先比较两个方向的队列大小,选择较小的队列所在的方向执行广度优先搜索,如果两个方向的队列大小相同则选择从 beginWord \textit{beginWord} beginWord 开始的方向执行广度优先搜索。

遍历过程中,如果发现当前方向的当前层的一个单词在另一个方向的已访问的哈希集合中,则两个方向的广度优先搜索相遇,存在从 beginWord \textit{beginWord} beginWord 到 endWord \textit{endWord} endWord 的转换序列,最短转换序列中的单词数目为已经遍历的轮数。

如果遍历结束之后两个方向的广度优先搜索仍未相遇,则不存在从 beginWord \textit{beginWord} beginWord 到 endWord \textit{endWord} endWord 的转换序列,返回 0 0 0。

代码

java 复制代码
class Solution {
    int wordLength;
    Set<String> wordSet = new HashSet<String>();

    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        for (String word : wordList) {
            wordSet.add(word);
        }
        if (!wordSet.contains(endWord)) {
            return 0;
        }
        wordLength = beginWord.length();
        Set<String> visitedBegin = new HashSet<String>();
        visitedBegin.add(beginWord);
        Set<String> visitedEnd = new HashSet<String>();
        visitedEnd.add(endWord);
        Queue<String> queueBegin = new ArrayDeque<String>();
        queueBegin.offer(beginWord);
        Queue<String> queueEnd = new ArrayDeque<String>();
        queueEnd.offer(endWord);
        int mutations = 0;
        while (!queueBegin.isEmpty() && !queueEnd.isEmpty()) {
            mutations++;
            boolean found;
            int sizeBegin = queueBegin.size(), sizeEnd = queueEnd.size();
            if (sizeBegin <= sizeEnd) {
                found = find(queueBegin, visitedBegin, visitedEnd);
            } else {
                found = find(queueEnd, visitedEnd, visitedBegin);
            }
            if (found) {
                return mutations;
            }
        }
        return 0;
    }

    public boolean find(Queue<String> queue, Set<String> visited1, Set<String> visited2) {
        int size = queue.size();
        for (int i = 0; i < size; i++) {
            String word = queue.poll();
            if (visited2.contains(word)) {
                return true;
            }
            List<String> adjacentWords = getAdjacentWords(word);
            for (String adjacent : adjacentWords) {
                if (wordSet.contains(adjacent) && visited1.add(adjacent)) {
                    queue.offer(adjacent);
                }
            }
        }
        return false;
    }

    public List<String> getAdjacentWords(String word) {
        List<String> adjacentWords = new ArrayList<String>();
        char[] arr = word.toCharArray();
        for (int i = 0; i < wordLength; i++) {
            char original = arr[i];
            for (char c = 'a'; c <= 'z'; c++) {
                if (c == original) {
                    continue;
                }
                arr[i] = c;
                adjacentWords.add(new String(arr));
            }
            arr[i] = original;
        }
        return adjacentWords;
    }
}

复杂度分析

  • 时间复杂度: O ( ∣ Σ ∣ × m × n ) O(|\Sigma| \times m \times n) O(∣Σ∣×m×n),其中 Σ \Sigma Σ 是字符集, m m m 是单词的长度, n n n 是字典的大小,这道题中 Σ \Sigma Σ 是全部小写英语字母, ∣ Σ ∣ = 26 |\Sigma| = 26 ∣Σ∣=26。广度优先搜索最多需要遍历每个单词一次,对于每个单词计算其下一层的单词的时间是 O ( ∣ Σ ∣ × m ) O(|\Sigma| \times m) O(∣Σ∣×m),因此时间复杂度是 O ( ∣ Σ ∣ × m × n ) O(|\Sigma| \times m \times n) O(∣Σ∣×m×n)。

  • 空间复杂度: O ( m × n ) O(m \times n) O(m×n),其中 m m m 是单词的长度, n n n 是字典的大小。哈希集合和队列需要 O ( m × n ) O(m \times n) O(m×n) 的空间。

相关推荐
伟大的车尔尼1 天前
搜索题目:最小基因变化
广度优先搜索
伟大的车尔尼4 天前
搜索题目:可能的二分法
并查集·深度优先搜索·广度优先搜索
伟大的车尔尼8 天前
搜索题目:边界着色
深度优先搜索·广度优先搜索
伟大的车尔尼13 天前
搜索题目:二进制矩阵中的最短路径
广度优先搜索
伟大的车尔尼15 天前
搜索题目:被围绕的区域
并查集·深度优先搜索·广度优先搜索
伟大的车尔尼21 天前
搜索题目:地图分析
动态规划·广度优先搜索
伟大的车尔尼22 天前
搜索题目:腐烂的橘子
广度优先搜索
伟大的车尔尼23 天前
搜索题目:01 矩阵
动态规划·广度优先搜索
伟大的车尔尼1 个月前
搜索题目:图像渲染
并查集·深度优先搜索·广度优先搜索