Leetcode127.单词接龙

news2024/11/15 21:42:42

https://leetcode.cn/problems/word-ladder/description/?envType=study-plan-v2&envId=top-interview-150

文章目录

  • 题目描述
  • 解题思路
  • 代码-BFS
  • 解题思路二——双向BFS
  • 代码

题目描述

字典 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
beginWord、endWord 和 wordList[i] 由小写英文字母组成
beginWord != endWord
wordList 中的所有字符串 互不相同

解题思路

  • 无向图中两个顶点之间的最短路径的长度,可以通过广度优先遍历得到;

  • 为什么 BFS 得到的路径最短?可以把起点和终点所在的路径拉直来看,两点之间线段最短;

  • 已知目标顶点的情况下,可以分别从起点和目标顶点(终点)执行广度优先遍历,直到遍历的部分有交集,这是双向广度优先遍历的思想。

  • 「转换」意即:两个单词对应位置只有一个字符不同,例如 “hit” 与 “hot”,这种转换是可以逆向的,因此,根据题目给出的单词列表,可以构建出一个无向(无权)图;
    在这里插入图片描述

  • 如果一开始就构建图,每一个单词都需要和除它以外的另外的单词进行比较,复杂度是 O(NwordLen),这里 N是单词列表的长度;当单词个数很多的时候,找到邻居的时间复杂度就很高了。

  • 为此,我们在遍历一开始,把所有的单词列表放进一个哈希表中,然后在遍历的时候构建图,每一次得到在单词列表里可以转换的单词,复杂度是 O(26×wordLen),借助哈希表,找到邻居与 N无关;

  • 使用 BFS 进行遍历,需要的辅助数据结构是:

    • 队列;
    • visited 集合。说明:可以直接在 wordSet (由 wordList 放进集合中得到)里做删除。但更好的做法是新开一个哈希表,遍历过的字符串放进哈希表里。这种做法具有普遍意义。绝大多数在线测评系统和应用场景都不会在意空间开销。

代码-BFS

class Solution {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        // 单向BFS写法
        // 第一步现将wordList放到哈希表中,便于判断某个单词是否在wordList中
        Set<String> wordSet = new HashSet<>(wordList);

        if(wordSet.size() == 0 || !wordSet.contains(endWord)){
            return 0;
        }
        // 把起点删了,以免节外生枝,不删提交也能过
        // wordSet.remove(beginWord);

        // 广度的队列
        Queue<String> queue = new LinkedList<>();
        queue.offer(beginWord);

        // 因为是字符串,所以要标记是否使用要用HashSet
        Set<String> visited = new HashSet<>();
        visited.add(beginWord);
        // 开始广搜,包含起点,因此初始化的步数为1
        int step = 1;
        while(!queue.isEmpty()){
            int currentSize = queue.size();
            for(int i=0; i< currentSize; i++){
                // 依次遍历当前队列中的单词
                String currentWord = queue.poll();
                // 尝试变异,修改其中的每一个字符,看看是否能与终态匹配
                for(int j=0; j<currentWord.length(); j++){
                    char[] originWord = currentWord.toCharArray();
                    char originChar = originWord[j];   // 先保存,再恢复,或者使用originWord.clone(),修改备份
                    for(char k = 'a'; k<= 'z'; k++){
                        if(originChar == k) // 和当前位置相同,跳过
                            continue;
                        originWord[j] = k;  // 尝试修改
                        String nextWord = String.valueOf(originWord); // 还要在换回字符串判断
                        if(wordSet.contains(nextWord)){
                            if(nextWord.equals(endWord)){
                                return step + 1;
                            }
                            if(!visited.contains(nextWord)){
                                queue.offer(nextWord);
                                visited.add(nextWord);  // 注意,添加到队列以后,必须马上标记为已访问
                            }
                        }
                    }
                    originWord[j] = originChar;  // 恢复
                }
            }
            step += 1;
        }
        return 0;
    }
}

解题思路二——双向BFS

  • 已知目标顶点的情况下,可以分别从起点和目标顶点(终点)执行广度优先遍历,直到遍历的部分有交集。这种方式搜索的单词数量会更小一些;
  • 优化一下,每次从单词数量小的集合开始扩散;
  • 这里 beginVisited 和 endVisited 交替使用,等价于单向 BFS 里使用队列,每次扩散都要加到总的 visited 里。
    在这里插入图片描述
    这样可以看出,使用双向BFS遍历的时候,访问的节点个数更少,当两侧的BFS交叉的时候就说明联通了。

代码

class Solution {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        // 双向BFS
        // 第 1 步:先将 wordList 放到哈希表里,便于判断某个单词是否在 wordList 里
        Set<String> wordSet = new HashSet<>(wordList);
        if(wordSet.size() == 0 || !wordSet.contains(endWord)){
            return 0;
        }

        // 已经访问过得word,添加到visited哈希表里
        Set<String> visited = new HashSet<>();

        //分别用左边和右边扩散的哈希表代替单向 BFS 里的队列,它们在双向 BFS 的过程中交替使用
        Set<String> beginVisited = new HashSet<>();
        beginVisited.add(beginWord);
        Set<String> endVisited = new HashSet<>();
        endVisited.add(endWord);

        visited.add(beginWord);
        visited.add(endWord);

        // 执行双向BFS,左右交替扩散步数之和为答案
        int step = 1;
        while(!beginVisited.isEmpty() && !endVisited.isEmpty()){
            // 优先选择小的哈希表进行扩散,考虑到的情况更少
            if(beginVisited.size() > endVisited.size()){
                Set<String> temp = beginVisited;
                beginVisited = endVisited;
                endVisited = temp;
            }
            // 逻辑到这里,保证 beginVisited 是相对较小的集合,nextLevelVisited 在扩散完成以后,会成为新的 beginVisited
            Set<String> nextLevelVisited = new HashSet<>();
            for(String word: beginVisited){

                char[] originWord = word.toCharArray();
                for(int i=0; i<word.length(); i++){
                    char originChar = originWord[i];
                    for(char k = 'a'; k<='z'; k++){
                        if(originWord[i] == k)
                            continue;
                        originWord[i] = k;
                        String nextWord = String.valueOf(originWord);
                        if(wordSet.contains(nextWord)){
                            if(endVisited.contains(nextWord)){   // 前后的BFS交叉了,说明连上了
                                return step + 1;
                            }

                            if(!visited.contains(nextWord)){
                                nextLevelVisited.add(nextWord);
                                visited.add(nextWord);
                            }
                        }
                    }
                    originWord[i] = originChar;
                }
            }
            // 原来的 beginVisited 废弃,从 nextLevelVisited 开始新的双向 BFS
            beginVisited = nextLevelVisited;
            step++;

        }
        return 0;
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1656621.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

django中的cookie与session

获取cookie request.COOKIE.GET 使用cookie response.set-cookie views.py from django.http import HttpResponse from django.shortcuts import render# Create your views here. def cookie_test(request):r HttpResponse("hello world")r.set_cookie(lan, py…

设计软件有哪些?渲染软件篇(2),渲染100邀请码1a12

好用的渲染软件有很多&#xff0c;今天我们接着介绍。 1、渲染100(http://www.xuanran100.com/?ycode1a12) 渲染100是网渲平台&#xff0c;为设计师提供高性能的渲染服务。通过它设计师可以把本地渲染移到云端进行&#xff0c;速度快价格便宜&#xff0c;支持3dmax、vray、c…

k8s 理论知识基本介绍

目录 一 k8s 理论前言 &#xff08;一&#xff09;微服务是什么 1&#xff0c;应用场景 2&#xff0c;API 是什么 &#xff08;二&#xff09;&#xff0c;微服务 如何做版本迭代 1. Docker镜像构建 2. 版本标记 3. Docker Registry 4. 环境一致性 5. 滚动更新…

《二十一》QT QML编程基础

QML概述 QML&#xff08;Qt Meta-Object Language&#xff09;是一种声明性语言&#xff0c;它被用于描述Qt框架中用户界面的结构和行为。QML提供了一种简洁、灵活的方式来创建动态和交互式的界面。 QML基于JavaScript语法&#xff0c;通过使用QML类型和属性来定义界面的元素…

[C++核心编程-01]----C++内存四区详细解析

目录 前言 正文 01-内存区域简介 02-全局区 03-栈区 04-堆区 05-new操作符 总结 前言 当程序运行时&#xff0c;操作系统会为程序分配一块内存空间&#xff0c;这块内存空间被划分为不同的区域&#xff0c;每个区域有其独特的作用…

ps5电玩计时收费系统软件教程,电玩店适合的计时器,电脑定时语音提醒

ps5电玩计时收费系统软件教程&#xff0c;电玩店适合的计时器&#xff0c;电脑定时语音提醒 一、前言 以下软件操作教程以&#xff0c;佳易王电玩计时计费管理软件为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 1、计时计费功能&#xff1a;只…

Linux内存管理——Swap

swap space 一个磁盘区域&#xff0c;作为内存使用。当系统内存不足时&#xff0c;会将一些很久不使用的数据转移到swap space中。 优点&#xff1a;扩展了内存空间 缺点&#xff1a;用磁盘做内存&#xff0c;读写效率降低。 swappiness swappiness的值表示建议swap space替…

GtkButton事件处理、事件的捕获、鼠标事件等

事件 事件处理 GTK 所提供的工具库与其应用程序都是基于事件触发机制来管理&#xff0c; 所有的应用程序都是基于事件驱动。 如果没有事件发生&#xff0c; 应用程序将处于等待状态&#xff0c; 不会执行任何操作&#xff0c; 一旦事件发生&#xff0c; 将根据不同的事件做出…

使用 YOLO 进行自定义对象检测

使用 YOLO 进行自定义对象检测 1. 创建数据集 机器是通过数据集学习的。数据集必须包含图像和标签。例如&#xff0c;让我的目标是创建一个检测坦克的系统。 我准备了从网上下载的坦克图片。然后我们需要使用第三方工具对图像进行标记&#xff0c;例如&#xff1b;LabelImg、…

SRM系统供应链库存协同提升企业服务水平

SRM系统供应链库存协同是一种以提高供应链整体效率和竞争力为目标的管理方法。它涉及到企业与供应商之间的紧密合作&#xff0c;以实现库存优化、成本降低、风险分担和灵活响应市场变化等目标。 一、SRM供应链库存协同的概念和特点 SRM供应链库存协同是指企业与供应商之间通过…

团结引擎+OpenHarmony 记录 (持续更新中)

1 TuanjiePlayerAbility.ts 中获取 content 引用 globalThis.AbilityContext 在 TuanjiePlayerAbility.ts 中是可以获取到的 但是在 tslib 或者中 globalThis.AbilityContext 是无法获取到的GetFromGlobalThis(‘AbilityContext’); 同样 在 TuanjiePlayerAbility.ts 中是可以…

文献速递:深度学习医学影像心脏疾病检测与诊断--基于深度学习的PET图像重建与运动估计

Title 题目 Deep Learning Based Joint PET Image Reconstruction and Motion Estimation 基于深度学习的PET图像重建与运动估计 01 文献速递介绍 正电子发射断层扫描&#xff08;PET&#xff09;成像是一种非侵入性成像技术&#xff0c;通过使用放射性示踪剂在活体内可视化…

UE4_摄像机_使用摄像机的技巧

学习笔记&#xff0c;不喜勿喷&#xff01;祝愿生活越来越好&#xff01; 知识点&#xff1a; a.相机跟随。 b.相机抖动。 c.摄像机移动 d.四元数插值&#xff08;保证正确旋转方向&#xff09;。 e.相机注视跟踪。 1、新建关卡序列&#xff0c;并给小车添加动画。 2、创…

C++奇迹之旅:string类接口详解(上)

文章目录 &#x1f4dd;为什么学习string类&#xff1f;&#x1f309; C语言中的字符串&#x1f309;string考察 &#x1f320;标准库中的string类&#x1f309;string类的常用接口说明&#x1f320;string类对象的常见构造 &#x1f6a9;总结 &#x1f4dd;为什么学习string类…

Unity Navigation 入门(新版)

概述 在游戏的制作过程中&#xff0c;寻路功能一定是非常重要的部分&#xff0c;他可以为主角寻路&#xff0c;也可以运用到敌人追击等&#xff0c;相比于自己实现的难度&#xff0c;使用寻路组件就显得简单的多&#xff0c;那接下来就开始学习这部分的内容吧 1.安装AI Naviga…

vue3打开页面后文本框自动获得焦点

字符串写法 <script setup> import { ref, onMounted } from vue import ./index.cssconst input ref(null)onMounted(() > {input.value.focus() }) </script><template><div class"m-home-wrap"><input ref"input" />…

[NSSRound#1 Basic]basic_check

[NSSRound#1 Basic]basic_check 开题什么都没有&#xff0c;常规信息搜集也无效 发现题目允许PUT的两种做法&#xff1a; 1、 CURL的OPTIONS请求方法查看允许的请求方式 curl -v -X OPTIONS http://node4.anna.nssctf.cn:28545/index.php2、 kali自带的nikto工具扫描网址 Nik…

已经有 Prometheus 了,还需要夜莺?

谈起当下监控&#xff0c;Prometheus 无疑是最火的项目&#xff0c;如果只是监控机器、网络设备&#xff0c;Zabbix 尚可一战&#xff0c;如果既要监控设备又要监控应用程序、Kubernetes 等基础设施&#xff0c;Prometheus 就是最佳选择。甚至有些开源项目&#xff0c;已经内置…

css z-Index 详解--子元素盖在父元素的兄弟元素上

前置知识 1、z-index 只有在定位元素上才会生效&#xff08;即非static定位的元素上&#xff09; 2、同级元素&#xff0c;无论是z-index 相同还是没设置。后面的元素层级比前面 3、元素上有 transform 属性 z-index 会失效 dom结构如下 // dom部分 <div><div id&quo…

数学建模资料|历年数维杯数学建模竞赛真题及获奖论文汇总

2024年第九届数维杯大学生数学建模挑战赛&#xff1a;2024年5月10日08:00-5月13日09:00举行&#xff0c;为了更好的帮助参赛同学了解竞赛的赛制及赛题特点&#xff0c;数乐君今天给大家整理了历年数维杯国赛真题及优秀论文&#xff0c;方便同学们赛前巩固训练&#xff0c;掌握解…