Leetcode127.单词接龙

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;
    }

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【HTTP下】总结{重定向/cookie/setsockopt/流操作/访问网页/总结}

文章目录 1.请求头2.cookie理解 3.vim跳转/搜索4.setsockopt被重用的意思 5.流操作5.1定位读取指针5.2ifstram::read() 6.总结6.1 百度搜索框搜索功能字符6.2请求uri请求和响应的第一行都有http版本请求内容里有GET /favicon.ico HTTP/1.1 6.3访问网页Fiddler抓包原理&#xff…

Hive SQL-DML-Load加载数据

Hive SQL-DML-Load加载数据 在 Hive 中&#xff0c;可以使用 SQL DML&#xff08;Data Manipulation Language&#xff09;语句中的 LOAD 命令来加载数据到表中。LOAD 命令用于将本地文件系统或 HDFS&#xff08;Hadoop 分布式文件系统&#xff09;中的数据加载到 Hive 表中。 …

0508_IO3

练习1&#xff1a; 1&#xff1a;使用 dup2 实现错误日志功能 使用 write 和 read 实现文件的拷贝功能&#xff0c;注意&#xff0c;代码中所有函数后面&#xff0c;紧跟perror输出错误信息&#xff0c;要求这些错误信息重定向到错误日志 err.txt 中去 1 #include <stdio.h…

数据可视化训练第一天(matplotlib直线;散点图,随机漫步)

前言 本人自己的练习记录&#xff1b;如有错误请指正&#xff1b; https://matplotlib.org/stable/gallery/lines_bars_and_markers/index.html 官方有许多例子&#xff0c;可以找到自己需要的图像模仿进行绘制 1.一个简单的直线例子 就如同我们学习C语言的第一个helloword时…

中间件的使用

中间件是全局使用 工厂函数定义中间件 middleware.py # 工厂函数的中间件 def simple_middleware(get_response):def middleware(request):print("在视图函数处理之前执行、、、、、")response get_response(request)print("在视图函数处理之后执行。。。。…

JavaSE——方法详解

1. 方法的概念 方法就是一个代码片段 . 类似于 C 语言中的 " 函数 " 。 方法存在的意义 : 1. 是能够模块化的组织代码(当代码规模比较复杂的时候). 2. 做到代码被重复使用, 一份代码可以在多个位置使用. 3. 让代码更好理解更简单. 4. 直接调用现有方法开发, 不…

YOLOv8训练流程-原理解析[目标检测理论篇]

关于YOLOv8的主干网络在YOLOv8网络结构介绍-CSDN博客介绍了&#xff0c;为了更好地学习本章内容&#xff0c;建议先去看预测流程的原理分析YOLOv8原理解析[目标检测理论篇]-CSDN博客&#xff0c;再次把YOLOv8网络结构图放在这里&#xff0c;方便随时查看。 ​ 1.前言 YOLOv8训练…

分布式光纤测温DTS的测温范围是多少?

分布式光纤测温DTS的测温范围不仅仅取决于光缆的感温能力&#xff0c;还受到多种复杂因素的影响。尽管高温光缆可以耐高温&#xff0c;低温光缆可以耐低温&#xff0c;甚至镀金光缆能够耐受高达700摄氏度的极高温度&#xff0c;然而&#xff0c;这些因素并不能完全解释测温范围…

平航杯复现

简单介绍及前期操作 esxi镜像挂载是一个新的创新点 就根据官方的wp进行挂载就可以了&#xff0c;后面差不多常规的服务器取证操作&#xff0c;然后服务器和计算机&#xff0c;u盘取证都有点联系&#xff0c;还是需要队友配合好一点 配置网段我的建议是把本机的配置改一下&am…

[windows系统安装/重装系统][step-1]U盘启动盘制作,微软官方纯净系统镜像下载

前言 U盘至少8GB吧我这刚好有个空闲的U盘8GB容量&#xff0c;制作启动盘且放入一个最新win10官方镜像足够 不是天天装系统&#xff0c;至少USB2.0 (我用的2.0的一个闲置U盘)即可&#xff0c;当然平时传资料什么的3.0会快些 U盘启动盘仅需要制作一次&#xff0c; U盘启动盘制…

python能够干什么?

python有哪些用途&#xff1f; Python是一种高级编程语言&#xff0c;它被广泛用于各种不同的领域。以下是Python的一些常见用途&#xff1a; 网络应用开发&#xff1a;Python可以用于编写Web应用程序、API、爬虫、网络服务器等。数据科学和机器学习&#xff1a;Python拥有许…

《intel开发手册卷1》学习笔记1

1、操作模式 IA-32架构支持三种基本操作模式:保护模式、实地址模式和系统管理模式。操作模式决定了哪些指令和体系结构功能是可访问的: 1&#xff09;保护模式&#xff1a;该模式是处理器的自然状态。保护模式的功能之一是能够在受保护的多任务环境中直接执行“实地址模式”80…

flask和django的对比

文章目录 1. 简介2. 安装和设置3. 路由和视图4. ORM5. 管理界面6. 社区和文档7. 性能结论 当涉及构建 Web 应用程序时&#xff0c;Flask 和 Django 是两个最受欢迎的 Python Web 框架之一。它们都提供了强大的工具和功能&#xff0c;但在某些方面却有所不同。本文将对 Flask 和…

链表的经典面试题(数据结构详解)+顺序表和链表之间区别+计算机存储体系

前言 首先这里已经正式步入数据结构的知识&#xff0c;之前我们已经讲解了链表的使用&#xff0c;接下来我们需要的就是大量的练习&#xff0c;熟练掌握数据结构。下面的题型我们选择的都是链表的经典题型&#xff0c;面试题型&#xff0c;包含快慢指针&#xff0c;数形结合&am…

Isaac Sim 3(学习笔记5.8)

Isaac Sim 利用深度学习获取mask掩码图 参考内容 Kubernetes官网 在 Linux 系统中安装并设置 kubectl | Kubernetes准备开始 kubectl 版本和集群版本之间的差异必须在一个小版本号内。 例如&#xff1a;v1.30 版本的客户端能与 v1.29、 v1.30 和 v1.31 版本的控制面通信。 用…

gocator导出图片

想用3D扫描后的图片&#xff0c;但是系统自带的导出方法很麻烦&#xff0c;所以考虑通过sdk导出 首先需要设置点云亮度 这里是导出图片的关键代码 case GoDataMessageType.SurfaceIntensity: { Debug.WriteLine("SurfaceIntensity "); GoSu…

AI换脸原理(2)——人脸检测参考文献S3FD:源码解析

1 介绍 S3FD是一个实时人脸检测器,这篇论文的主要思想是试图解决一个常见的问题,即基于anchor(锚点)的检测器随着人脸变小而急剧恶化。 基于锚点的目标检测方法是通过对一系列预设锚点进行分类和回归来检测目标的,这些锚点是通过在图像上有规律地平铺一组不同尺度和宽高比…

凡尔码安全巡检卡替代传统纸质记录卡

建筑行业、物业管理、医院等行业的安全巡检的记录方式通常以&#xff1a;1、纸质记录&#xff1a;巡检人员使用纸质巡检表格&#xff0c;手动填写巡检时间、巡检区域、巡检发现的问题以及处理情况。这种方式简单直接&#xff0c;但可能存在信息记录不完整、易丢失等问题。 2、电…

rust打包编译为mac或者linux可执行文件,发送到别的电脑不能运行

如果使用rust项目编译为linux或者mac可执行文件&#xff0c;发送到别的电脑之后&#xff0c;不可以直接运行&#xff0c;而是显示一个空白文件&#xff0c;双击也没有反应&#xff0c;其实这是因为这个文件没有可执行权限导致的&#xff0c;添加可执行权限就可以了&#xff1a;…

用户至上!探索7种常用的用户体验研究方法

用户体验研究是产品开放过程中的重要组成部分&#xff0c;优秀的产品设计与高质量的用户体验是不可分割的。对于产品开发&#xff0c;选择合适的用户体验研究方法在很大程度上决定了产品的使用效果。本文全面阐述了用户体验研究、用户体验研究的重要性和用户体验研究方法&#…