【LeetCode】127. 单词接龙(Java)

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

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

说明:

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

这道题没思路,看了weiwei哥的题解学会的,这道题用到bfs+回溯,优化用了双向bfs,又学到新知识,赚到了。
weiwei哥的题解:https://leetcode-cn.com/problems/word-ladder/solution/yan-du-you-xian-bian-li-shuang-xiang-yan-du-you-2/

解法一

bfs + 回溯

class Solution {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        // bfs + 回溯
        // 把wordList存到wordSet中,如果wordSet为空,或者不存在endWord,返回0
        Set<String> wordSet = new HashSet<>(wordList);
        if (wordSet.size() == 0 || !wordSet.contains(endWord)) {
            return 0;
        }
        // 移除wordSet中的beginWord,如果有的话
        wordSet.remove(beginWord);

        // bfs使用队列,访问过的单词需要保存到visited中
        // 创建队列,并把beginWord保存到队列中,起始位
        Queue<String> queue = new LinkedList<>();
        queue.offer(beginWord);
        // 创建名为visited的set,用于保存已访问的单词
        Set<String> visited = new HashSet<String>();
        visited.add(beginWord);

        // 转换次数从1开始,因为题意中说明beginWord和endWord是不同的
        int step = 1;
        while (!queue.isEmpty()) {
            // 逐层遍历
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                // 如果当前单词转换后为endWord,那么返回转换次数加1
                String word = queue.poll();
                if (changeWordEveryOneLetter(word, endWord, queue, visited, wordSet)) {
                    return step + 1;
                }
            }
            step++;
        }
        return 0;
    }

    // 回溯
    // 用回溯去改变当前单词,如果改变后为endWord,返回true
    private boolean changeWordEveryOneLetter(String word, String endWord, Queue<String> queue, Set<String> visited, Set<String> wordSet) {
        // 把当前单词转为char数组,遍历修改每个字符
        char[] ch = word.toCharArray();
        for (int i = 0; i < ch.length; i++) {
            // 保存当前单词,用于跳过和回溯使用
            char c = ch[i];
            // 每个字符从a修改到z
            for (char k = 'a'; k <= 'z'; k++) {
                // 如果与当前字符相同,跳过
                if (k == c) {
                    continue;
                }
                // 修改字符
                ch[i] = k;
                // 如果转换后的单词存在wordSet中
                String changeWord = String.valueOf(ch);
                if (wordSet.contains(changeWord)) {
                    // 如果转换后的单词为endWord,返回true
                    if (changeWord.equals(endWord)) {
                        return true;
                    }
                    // 如果转换后的单词不在visited中,保存到队列中,并保存到visited中
                    if (!visited.contains(changeWord)) {
                        queue.offer(changeWord);
                        visited.add(changeWord);
                    }
                }
            }
            // 恢复,变为原来的值
            ch[i] = c;
        }
        return false;
    }
}

在这里插入图片描述

解法二

双向bfs + 回溯,双向bfs把只有一个结果变为一个结果集,效率更高,最优解。

class Solution {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        // 双向bfs + 回溯
        // 把wordList存到wordSet中,如果wordSet为空,或者不存在endWord,返回0
        Set<String> wordSet = new HashSet<>(wordList);
        if (wordSet.size() == 0 || !wordSet.contains(endWord)) {
            return 0;
        }

        // 创建名为visited的set,用于保存已访问的单词
        Set<String> visited = new HashSet<String>();
        // 用两个set代替队列,这里我觉得用work和idle来命名比较好,weiwei哥那么命名我觉得会难理解些
        // 因为两个会交换,只有一个在工作中,beginVisited和endVisited命名会难理解它们会交换
        Set<String> work = new HashSet<String>();
        work.add(beginWord);
        Set<String> idle = new HashSet<String>();
        idle.add(endWord);

        int step = 1;
        while (!work.isEmpty() && !idle.isEmpty()) {
            // 需要保证每个进行替换的set个数是最少的,减少运算次数
            if (work.size() > idle.size()) {
                Set<String> temp = work;
                work = idle;
                idle = temp;
            }

            // 符合条件的单词会保存到newSet
            Set<String> newSet = new HashSet<>();
            for (String word : work) {
                if (changeWordEveryOneLetter(word, idle, visited, wordSet, newSet)) {
                    return step + 1;
                }
            }
            // 替换为新的set,因为work中的单词都访问过了
            work = newSet;
            step++;
        }
        return 0;
    }
    

    // 用回溯去改变当前单词,如果改变后的单词存在于idle中,返回true
    private boolean changeWordEveryOneLetter(String word, Set<String> idle, 
                                             Set<String> visited, Set<String> wordSet,
                                             Set<String> newSet) {
        // 把当前单词转为char数组,遍历修改每个字符
        char[] ch = word.toCharArray();
        for (int i = 0; i < ch.length; i++) {
            // 保存当前单词,用于跳过和回溯使用
            char c = ch[i];
            // 每个字符从a修改到z
            for (char k = 'a'; k <= 'z'; k++) {
                // 如果与当前字符相同,跳过
                if (k == c) {
                    continue;
                }
                // 修改字符
                ch[i] = k;
                // 如果转换后的单词存在wordSet中
                String changeWord = String.valueOf(ch);
                if (wordSet.contains(changeWord)) {
                    // 如果转换后的单词为endWord,返回true
                    if (idle.contains(changeWord)) {
                        return true;
                    }
                    // 如果转换后的单词不在visited中,保存到newSet中,并保存到visited中
                    if (!visited.contains(changeWord)) {
                        newSet.add(changeWord);
                        visited.add(changeWord);
                    }
                }
            }
            // 恢复,变为原来的值
            ch[i] = c;
        }
        return false;
    }
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值