《剑指 Offer》专项突破版 - 面试题 108 : 单词演变(C++ 实现)

目录

前言

单向广度优先搜索

双向广度优先搜索


 


前言

题目链接:单词演变

题目

输入两个长度相同但内容不同的单词(beginWord 和 endWord)和一个单词列表(wordList),求从 beginWord 到 endWord 的演变序列的最短长度,要求每步只能改变单词中的一个字母,并且演变过程中每步得到的单词都必须在给定的单词列表中。如果不能从 beginWord 演变到 endWord,则返回 0。假设所有单词只包含英文小写字母。

例如,如果 beginWord 为 "hit",endWord 为 "cog",wordList 为 ["hot", "dot", "dog", "lot", "log", "cog"],则演变序列的最短长度为 5,一个可行的演变序列为 "hit"->"hot"->"dot"->"dog"->"cog"。

分析

应用图相关算法的前提是找出图中的节点和边。这个问题是关于单词的演变的,所以每个单词就是图中的一个节点。如果两个单词能够相互演变(改变一个单词的一个字母就能变成另一个单词),那么这两个单词之间有一条边相连。例如,可以用下图表示 "hit"、"hot"、"dot"、"dog"、"lot"、"log"、"cog" 的演变关系,可以看出,从 "hit" 演变成 "cog" 的最短序列的长度为 5,一个可行的最短序列经过的节点用阴影表示。


单向广度优先搜索

这个题目要求计算最短演变序列的长度,即求图中两个节点的最短距离。表示单词演变的图也是一个无权图,按照题目的要求,图中两个节点的距离是连通两个节点的路径经过的节点的数目。通常用广度优先搜索计算无权图中的最短路径,广度优先搜索通常需要用到队列。

为了求得两个节点之间的最短距离,常见的解法是用两个队列实现广度优先搜索算法。一个队列 q1 中存放离起始节点距离为 d 的节点,当从这个队列中取出节点并访问的时候,与队列 q1 中相邻的节点离起始节点的距离都是 d + 1,将这些相邻的节点存放到另一个队列 q2 中。当队列 q1 中的所有节点都访问完毕时,再访问队列 q2 中的节点,并将相邻的节点放入 q1 中。可以交替使用 q1 和 q2 这两个队列由近及远地从起始节点开始搜索所有节点

class Solution {
public:int ladderLength(string beginWord, string endWord, vector<string>& wordList) {unordered_set<string> notVisited(wordList.begin(), wordList.end());queue<string> q1, q2;q1.push(beginWord);int length = 1;while (!q1.empty()){string word = q1.front();q1.pop();if (word == endWord)return length;vector<string> neighbors = getNeighbors(word);for (string& neighbor : neighbors){if (notVisited.count(neighbor)){q2.push(neighbor);notVisited.erase(neighbor);}}
​if (q1.empty()){q1 = q2;q2 = queue<string>();++length;}}return 0;}
private:vector<string> getNeighbors(string& word) {vector<string> neighbors;for (int i = 0; i < word.size(); ++i){char old = word[i];for (char ch = 'a'; ch <= 'z'; ++ch){if (ch != old){word[i] = ch;neighbors.push_back(word);}}word[i] = old;}return neighbors;}
};

上述代码首先将起始节点 beginWord 添加到队列 q1 中。接下来每一步从队列 q1 中取出一个节点 word 访问。如果 word 就是目标节点,则搜索结束;否则找出所有与 word 相邻的节点并将相邻的节点放到队列 q2 中。当队列 q1 中的所有节点都访问完毕时交换队列 q1 和 q2,以便接下来访问原来存放在队列 q2 中的节点。每次交换队列 q1 和 q2 都意味着距离初始结点距离为 d 的节点都访问完毕,接下来将访问距离为 d + 1 的节点,因此距离值增加 1

上述代码将单词列表中还没有访问过的节点放入 notVisited 中,每当一个单词被访问过就从这个 unordered_set 中删除。如果一个节点不在 notVisited 中,要么它不在单词列表之中,要么之前已经访问过,不管是哪种情况这个节点都可以忽略


双向广度优先搜索

这个题目是关于单一起始节点、单一目标节点的最短距离问题。前面的解法是从起始节点出发不断朝着目标节点的方向搜索,直到到达目标节点。针对这类问题有一种常用的优化方法,即在从起始节点出发不断朝着目标节点的方向搜索的同时,也从目标节点出发不断朝着起始节点的方向搜索。这种双向搜索的方法能够缩小搜索空间,从而提高搜索效率

下图是双向广度优先搜索缩小搜索空间的示意图。假设目标是求出图中顶部的黑色节点到底部的黑色节点的最短距离。如果采用广度优先搜索,那么图中所有节点都可能会被搜索到,如下图 (a) 所示。如果采用双向广度优先搜索,则分别从起始节点和目标节点出发不断搜索,直到在中间某个位置相遇,那么图中只有部分节点被搜索到,如下图 (b) 所示。

class Solution {
public:int ladderLength(string beginWord, string endWord, vector<string>& wordList) {unordered_set<string> notVisited(wordList.begin(), wordList.end());if (notVisited.count(endWord) == 0)return 0;unordered_set<string> s1, s2;s1.insert(beginWord), s2.insert(endWord);int length = 2;while (!s1.empty() && !s2.empty()){if (s2.size() < s1.size())s1.swap(s2);unordered_set<string> s3;for (const string& word : s1){vector<string> neighbors = getNeighbors(word);for (string& neighbor : neighbors){if (s2.count(neighbor))return length;if (notVisited.count(neighbor)){s3.insert(neighbor);notVisited.erase(neighbor);}}}++length;s1 = s3;}return 0;}
private:vector<string> getNeighbors(const string& word) {string tmp = word;vector<string> neighbors;for (int i = 0; i < tmp.size(); ++i){char old = tmp[i];for (char ch = 'a'; ch <= 'z'; ++ch){if (ch != old){tmp[i] = ch;neighbors.push_back(tmp);}}tmp[i] = old;}return neighbors;}
};

在上述代码中,s1 和 s2 分别存放两个方向上当前需要访问的节点,s3 用来存放与当前访问的节点相邻的节点。之所以这里用的是 unordered_set 而不是 queue,是因为需要判断从一个方向搜索到的节点在另一个方向是否已经访问过。只需要 O(1) 的时间就能判断 unordered_set 中是否包含一个元素

先将起始节点 beginWord 添加到 s1 中,将目标节点 endWord 添加到 s2 中。接下来每次 while 循环都是从需要访问的节点数目少的方向搜索,这样做是为了缩小搜索的空间。先确保 s1 中需要访问的节点数更少,接下来访问 s1 中的每个节点 word。如果某个与节点 word 相邻的节点 neighbor 在 s2 中,则说明两个不同方向的搜索相遇,已经找到了一条起始节点和目标节点之间的最短路径,此时路径的长度就是它们之间的最短距离,否则将节点 neighbor 添加到 s3 中。当 s1 中所有的节点都访问完毕,接下来可能会访问 s1 的节点的相邻节点,即 s3 中的节点,因此将 s1 指向 s3。然后继续从 s1 和 s2 中选择一个节点数目少的方向进行新一轮的搜索。每轮搜索都意味着在起始节点和目标节点之间的最短路径上多前进了一步,因此变量 length 增加 1

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软考高级架构师:随机函数模型

一、AI 讲解 随机函数模型是理解各种随机过程和算法的一个重要概念&#xff0c;在软件工程、算法设计以及系统分析中有着广泛的应用。简而言之&#xff0c;随机函数模型是一种用于描述具有随机性的系统或过程的数学模型&#xff0c;它能够帮助我们预测和分析在不确定性下的系统…

一起学习python——基础篇(19)

今天来说一下python的如何修改文件名称、获取文件大小、读取文中指定的某一行内容。 1、修改文件名称&#xff1a; import os testPath"D:/pythonFile/test.txt" testPath2"D:/pythonFile/test2.txt" #修改文件名称使用rename方法&#xff0c; #第一个参…

数字中台建设全套方案资料(ppt+word)

数据中台业务方案及详细技术方案资料&#xff1a; 1. 数据中台解决方案PPT&#xff08;本篇内容&#xff09; 2. 数字中台建设指南详细技术方案 数字中台建设指南Word版原件技术方案&#xff1a;进入文章 软件开发全资料获取通道①&#xff1a;软件项目开发全套文档下载_软件开…

基于spring boot的留守儿童爱心管理系统

基于spring boot的留守儿童爱心管理系统设计与实现 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开…

Java | Leetcode Java题解之第25题K个一组翻转链表

题目&#xff1a; 题解&#xff1a; class Solution {public ListNode reverseKGroup(ListNode head, int k) {ListNode hair new ListNode(0);hair.next head;ListNode pre hair;while (head ! null) {ListNode tail pre;// 查看剩余部分长度是否大于等于 kfor (int i 0…

万字长文深入理解Docker镜像分层原理、容器数据卷、网络通信架构(Docker系列第2章,共3章)

镜像分层的简单直观体现 在执行docker pull时&#xff0c;会发现多个Pull complete 字样&#xff0c;就能体现分层&#xff0c;如果是一个文件&#xff0c;只会有一个Pull complete 。 docker pull redis Using default tag: latest latest: Pulling from library/redis a2ab…

03 Git 之 远程仓库 + IDEA 集成使用 GitHub

1. 远程仓库 origin&#xff1a;即远程仓库 url 的指代。 从网上随意 clone 一个仓库&#xff0c;进入 .git/config 文件, 即可编辑远程仓库的 url&#xff0c;也可以自定义想要指代该 url 的名词。 1.1 本地仓库绑定远程仓库 并 推送、拉取 git remote add 【想要起的指代…

Golang | Leetcode Golang题解之第27题移除元素

题目&#xff1a; 题解&#xff1a; func removeElement(nums []int, val int) int {left, right : 0, len(nums)for left < right {if nums[left] val {nums[left] nums[right-1]right--} else {left}}return left }

连接参数a和b operator.concat(a, b)

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 连接参数a和b operator.concat(a, b) [太阳]选择题 以下关于operator.concat()的功能和作用说法正确的是&#xff1a; import operator print("【执行】operator.concat(Hello, World)&q…

ES6: promise对象与回调地狱

ES6&#xff1a; promise对象与回调地狱 一、回调地狱二、Promise概述三、Promise的组成四、用函数封装Promise读取文件操作 一、回调地狱 在js中大量使用回调函数进行异步操作&#xff0c;而异步操作什么时候返回结果是不可控的&#xff0c;所以希望一段程序按我们制定的顺序执…

Python | Leetcode Python题解之第26题删除有序数组中的重复项

题目&#xff1a; 题解&#xff1a; class Solution:def removeDuplicates(self, nums: List[int]) -> int:if not nums:return 0n len(nums)fast slow 1while fast < n:if nums[fast] ! nums[fast - 1]:nums[slow] nums[fast]slow 1fast 1return slow

009、Python+fastapi,第一个后台管理项目走向第9步:ubutun 20.04下安装vscode+git环境配置

一、说明 git是一定要配置的&#xff0c;由于是白嫖的无影云电脑&#xff0c;东西得保存在网上&#xff0c;就继续白嫖gitee吧&#xff0c;显然国内github是不太合适的了 二、安装git 直接安装sudo apt install -y git git --version git version 2.25.1 三、配置git 在git上…