Java_单源_BFS_单词接龙

186 阅读5分钟

题目链接: leetcode.cn/problems/wo…

题目描述:

字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk

  • 每一对相邻的单词只差一个字母。
  •  对于 1 <= i <= k 时,每个 si 都在 wordList 中。注意, beginWord 不需要在 wordList 中。
  • sk == endWord

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

 

示例 1:

输入: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] 输出: 5 解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。

示例 2:

输入: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"] 输出: 0 解释: endWord "cog" 不在字典中,所以无法进行转换。

提示:

  • 1 <= beginWord.length <= 10
  • endWord.length == beginWord.length
  • 1 <= wordList.length <= 5000
  • wordList[i].length == beginWord.length
  • beginWordendWord 和 wordList[i] 由小写英文字母组成
  • beginWord != endWord
  • wordList 中的所有字符串 互不相同

解析

拿到题目后第一时间是很难想到 BFS 的,这个只能通过刷经验来提升了。不过想到 BFS 之后这题就很好实现,只需要处理好几个细节。 先理解题意吧,给定两个单词,起始单词为 beginWord ,终点单词为 endWord (这两个单词的长度是一样的,并且给定的时候是不相等的),我们需要将起始单词通过替换字母的方式,转换为 endWord,而且不是随意变换的,每次只能换一个字母,替换完的单词还必须出现在给定的词库中,然后重复此过程。最终找到替换次数最少的方式,并返回替换的单词数目(包含beginWord)。 如果把这题转换一下:从起始点出发,每次走一格,请找到最短到达终点的路径,并返回步数。 熟悉了吧,其实大差不差。

直接举例

如上图,我们初始的单词是 hit,想要变成 cog。(因为要一步一步讲,过程中你可能会揣着问题,但是看完整篇内容你会懂的,先别急) 我们需要修改的字母有三个,我们可以先改第一个字母,也可以改第二个或者第三个,但是我们修改完的单词必须要在库中。比如 cit 是修改了第一个字母,但是库中没有,hig 修改了第三个字母,库中也没有,所以这两条路就算断了,无法再往下走,那么修改第二个字母是可以的。修改 i 为 o,库中是有 hot 这个单词的(绿色分支)。找到了 hot 这个单词就相当于走迷宫中找到一条路是一样的,只是第一轮 BFS 而已。下面就需要把 hot 拿出来,看看能不能再找到其他的分支。

如上图,hot 继续往下 BFS 我们还是可以有很多的变换方法,但是替换完字母单词都不在库中(cot、hog),所以只能舍弃,而能使用的就是 dot 和 lot 这两个单词了(绿色分支),他们都在库中。我们继续使用这俩个单词往下 BFS。 dot 此时怎么变化库中都不含有它的单词(当然是排除再次变化为 hot ,此方法直接排除掉,待会讲细节会讲到), 所以只剩下 lot 这一个分支了,发现可以变化为 log 最后变化为 cog。一共是五个单词(包含 hit 本身),返回 5。

这时候问题就来了,我们替换的字母是怎么来的?从 a - z 随便选的吗?其实从 a 遍历到 z 也可以,但是不是最优解。我们变化后的单词必须包含在库中,那么我们直接将单词库中所含的字母拿出来,作为一个字母库,使用字母库就可以了,库中都没有的字母,你组成的单词库中自然也不含有。

还有处理重复出现过的单词,我们修改过的单词就不要在进行重复回滚了,就比如 dot 可以回滚为 hot,但是这样毫无意义,因为这条路绕一个圈之后肯定是笔 hot-lot-log 更长的,直接舍弃掉就可以了。直接使用 Set 来存储已经修改过的单词(删除重复的值)。 还有一个细节,就是我们在判断库中是否含有这个单词的时候,是需要遍历的,我们不如直接使用 Set 然后使用 contains 方法来判断 下面我们来总结一下此题目的所有步骤:

  1. 获取到库中的单词
  2. 获取到库中的所有字母
  3. 创建一个库,用于存储修改过的单词(判断是否重复)
  4. 创建 queue 队列,将 beginWord 存储到其中,等候BFS
  5. 创建 step 用于记录修改过字母的个数
  6. 开始BFS
  7. 返回 下面就开始写代码

代码

class Solution {

    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        //存储所有单词
        Set<String> words = new HashSet<>();
       
        //存储所有字母
        Set<Character> alphabet = new HashSet<>();

        //判断是否修改过
        Set<String> isExist = new HashSet<>();

        //拿到所有的单词和字母
        for (String str : wordList) {
            //拿到单词
            words.add(str);
            
            char[] cur = str.toCharArray();
            for (char tmp : cur) {
	            //拿到字母
                alphabet.add(tmp);
            }
        }
        //如果单词中不含有endWord的话直接返回0,不含有endword就说明无解了
        if (!words.contains(endWord)) return 0;

        //创建queue
        Queue<String> queue = new LinkedList<>();
        queue.add(beginWord);
        
        //创建单词个数(因为包含beginWord本身,所以初始化为1)
        int step = 1;
        //开始BFS
        while (!queue.isEmpty()) {
            //步数++
            step++;
            //拿到所有分支
            int size = queue.size();
            //遍历所有分支
            while (size-- != 0) {
            
                String word = queue.poll();
                for (int i = 0 ; i < word.length(); i++) {
                    //开始替换字母
                    char[] charWord = word.toCharArray();
                    for (char c : alphabet) {
                        //替换字母
                        charWord[i] = c;
                        //将字符数组转换为字符串
                        String newWord = new String(charWord);
                        //判断修改过的单词是否等于endWord,是的话直接返回步数
                        if (newWord.equals(endWord)) return step;
                        //判断是否在库中且是否使用过这个单词
                        if (words.contains(newWord) && !isExist.contains(newWord)) {
                            //没有使用过且在库中的话就将他们放到队列中(找到分支)再放到使用过的库中
                            queue.add(newWord);
                            isExist.add(newWord);
                        }
                    }
                }
            }
        }
        //走完BFS说明没有结果,直接返回0
        return 0;
    }
}