127. 单词接龙

69 篇文章 1 订阅
63 篇文章 0 订阅

给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:

每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。

说明:

如果不存在这样的转换序列,返回 0。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。

示例 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” 不在字典中,所以无法进行转换。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/word-ladder
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方法一:广度优先搜索法(参考官方答案)
思路:每个单词如果逐个替换其中一个字母为’’,就可以将每个单词替换得到的新单词和原单词对应起来,但是有可能存在重复情况,例如:dog,dig都可以替换得到dg,所以对应关系原单词应该用链表存放,然后就开始拿着startWord去替换,和存放所以可能的map对比,相等则取出临近单词列表,继续比对,记住所有单只能使用一次,做标记,每走一步则步数加一,直到找到临近列表中存在endWord,则说明已经找到结果,返回步数即可;
步骤:
1,先将wordList中的所有单词替换,找到所有临近节点链表,对应起来,用Map all存放;
2,使用队列,将新单词和初始步数1进入队列,然后取出队列头第一个键值对,键是当前单词,值是当前步数,如果该键值存在all里面,说明当前单词存在临近单词,也就是只替换一个字母就能得到的单词,然后判断all里面这个键值对应的list中是否存在endWord,如果存在则说明,当前单词只需要在转换一个字母即可得到endWord,返回结果当前步数加一即可;如果不存在,则判断这个列表中的单词是否已被使用,使用则加入标记数组,否则就将该单词和步数加一放入队列进行下一轮判断;
3,直到队列为空则结束,如果不包括则返回0;

方法二:双向BFS
思路:由于方法一,需要逐个判断,如果每个单词对应的替换情况多,数据量太大,需要优化;方法一是从startWord开始,每次如果有临近单词列表,则逐个判断,不考虑列表内容多少;现在优化为双向开始,也就是判断是startWord对应的临近单词列表大还是endWord大,那个小则从那个开始判断,其实每次还是单向搜索,只是有了选择性,都是从省力的一方开始;
步骤:
1,需要三个HashSet,一个存放当前startWord 对应的临近单词set start,一个存放endWord对应的临近单词set end,还有一个为all,存放所有单词列表,步数初始值为2,因为已经各自走了一步;
2,归根结底每次都还是从start 中拿单词进行判断查找临近列表,所有如果start为空时,说明已经遍历完,如果end比start大,则互换,让end成为start;
3,将start中的每个单词拿出来,(每个单词只能使用一次,每次遍历start时记得要将all的所有start中的元素删除;)逐个替换每个字母从a - z,判断其是否存在于all中,存在则向当前单词的临近列表temp中添加,如果此时end中也存在,则说明只需要再走一步即是结果;
4,当前start遍历完成后,如果没有找到结果,将temp变成start,步数加一,继续进行下一轮直到找到结果!

import javafx.util.Pair;
class Solution {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
      //  return ladder1(beginWord, endWord, wordList);
        return ladder2(beginWord, endWord, wordList);
    }
    //广度优先搜素
    private int ladder1(String beginWord, String endWord, List<String> wordList) {
        //特殊情况
        if (wordList == null || wordList.size() == 0)
            return 0;
        if (!wordList.contains(endWord))
            return 0;
        //将wordListzhong 每个单词所能替代的所有情况列出来
        int L = beginWord.length();
        Map<String, List<String>> all = new HashMap<>();
        wordList.forEach(word -> {
            //将每个单词对应情况填入hashmap中
            for (int i = 0; i < L; i ++) {
                String newWord = word.substring(0,i) + '*' + word.substring(i + 1, L);
                //存在一对多
                List<String> trans = all.getOrDefault(newWord, new ArrayList<String>());
                trans.add(word);
                all.put(newWord, trans);
            }
        });
        //使用队列进行BFS
        Queue<Pair<String,Integer>> q = new LinkedList<Pair<String, Integer>>();
        //防止对同一个单词重复操作
        List<String> check = new ArrayList<String>();
        //刚开始为1
        q.add(new Pair(beginWord,1));
        while(!q.isEmpty()) {
            Pair<String,Integer> node = q.remove();//取出头
            String word = node.getKey();
            int level = node.getValue();
            //找出当前word能匹配到的currenWord
            for (int i = 0; i < L; i ++) {
                //如果匹配到,并且这个单词对应的元单词列表中存在endWord,则结果就是level+1;
                String newWord = word.substring(0,i) + '*' + word.substring(i+1,L);
                //判断newWord是否是all中的一个键
                for (String adWord : all.getOrDefault(newWord, new ArrayList<String>())) {
                    //列表存在endWord,则结束
                    if (adWord.equals(endWord))
                        return level + 1;
                    //如果该单词未被使用则放入队列,否则跳过
                    if (!check.contains(adWord)) {
                        check.add(adWord);
                        q.add(new Pair(adWord,level + 1));
                    }
                }
            }
        }
        return 0;
    }
       //双向广度优先搜索,两边同时搜索
    private int ladder2(String beginWord, String endWord, List<String> wordList) {
        //特殊情况
        if (wordList == null || wordList.size() == 0)
            return 0;
        if (!wordList.contains(endWord))
            return 0;
        //防止重复,使用hashset;
        Set<String> start = new HashSet<>();
        Set<String> end = new HashSet<>();
        //存放所有单词
        Set<String> all = new HashSet<>(wordList);
        start.add(beginWord);
        end.add(endWord);
        //双向搜索,所以初始步数即为2
        return bfs(start, end, all, 2);
    }
    //双向BFS,谁近走谁的路线,每次替换一个字母
    //递归
    private int bfs(Set<String> start, Set<String> end, Set<String> all, int l) {
        //结束条件,出现断裂,找不到合适的路径走下一步,则结束
        if (start.size() == 0)
            return 0;
        //那一端可能性少从那端出发
        if (start.size() > end.size())
            return bfs(end, start, all, l);
        //其实还是从一端进行搜索而已,只是每次都从少的一段开始,不重复使用,用了就移除
        all.removeAll(start);
        //每次都是改变一个字母然后搜索所有临近点
        Set<String> temp = new HashSet<>();
        for (String s : start) {
            //变化每个字母,注意记录原值,要回档
            char[] arr = s.toCharArray();
            for (int i = 0; i < arr.length; i ++) {
                char tmp = arr[i];
                //重复跳过
                for (char c = 'a'; c <= 'z'; c ++) {
                    if (c == tmp)
                        continue;
                    arr[i] = c; 
                    String str = new String(arr);
                    //如果该单词存在于源列表,判断
                    if (all.contains(str)) {
                        //如果结果集中也有,则说明找到
                        if (end.contains(str))
                            return l;
                        //否则加入临近列表
                        temp.add(str);
                    }
                    arr[i] = tmp;
                }
            }
        }
        //start变为temp开始下一轮
        return bfs(temp, end, all, l + 1);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值