LeetCode 332. Reconstruct Itinerary【欧拉回路,通路,DFS】困难

news2024/9/28 13:15:58

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。

为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。

由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。

给你一份航线列表 tickets ,其中 tickets[i] = [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。

所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。

  • 例如,行程 ["JFK", "LGA"] 与 ["JFK", "LGB"] 相比就更小,排序更靠前。

假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次只能用一次

示例 1:

输入:tickets = [["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]]
输出:["JFK","MUC","LHR","SFO","SJC"]

示例 2:

输入:tickets = [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
输出:["JFK","ATL","JFK","SFO","ATL","SFO"]
解释:另一种有效的行程是 ["JFK","SFO","ATL","JFK","ATL","SFO"] ,但是它字典排序更大更靠后。

提示:

  • 1 <= tickets.length <= 300
  • tickets[i].length == 2
  • fromi.length == 3
  • toi.length == 3
  • fromi 和 toi 由大写英文字母组成
  • fromi != toi

本题和「753. 破解保险箱」类似,是力扣平台上为数不多的求解欧拉回路 / 欧拉通路的题目。

我们化简本题题意:给定一个 O ( n ) O(n) O(n) 个点 O ( m ) O(m) O(m) 条边的图,要求从指定的顶点出发,经过所有的边恰好一次(可以理解为给定起点的「一笔画」问题),使得路径的字典序最小。这种「一笔画」问题与欧拉图或者半欧拉图有着紧密的联系,下面给出定义:

  • 通过图中所有边恰好一次且行遍所有顶点的通路称为欧拉通路;
  • 通过图中所有边恰好一次且行遍所有顶点的回路称为欧拉回路;
  • 具有欧拉回路的无向图称为欧拉图;
  • 具有欧拉通路但不具有欧拉回路的无向图称为半欧拉图

因为本题保证至少存在一种合理的路径,也就告诉了我们,这张图是一个欧拉图或者半欧拉图。我们只需要输出这条欧拉通路的路径即可。如果没有保证至少存在一种合理的路径,我们需要判别这张图是否是欧拉图或者半欧拉图,具体地:

  • 对于无向图 G G G G G G 是欧拉图当且仅当 G G G 是连通的且没有奇度顶点
  • 对于无向图 G G G G G G 是半欧拉图当且仅当 G G G 是连通的且 G G G 中恰有 0 0 0 个或 2 2 2 个奇度顶点
  • 对于有向图 G G G G G G 是欧拉图当且仅当 G G G 的所有顶点属于同一个强连通分量每个顶点的入度和出度相同
  • 对于有向图 G G G G G G 是半欧拉图当且仅当:
    • 如果将 G G G 中的所有有向边退化为无向边时,那么 G G G 的所有顶点属于同一个强连通分量;
    • 最多只有一个顶点的出度与入度差为 1 1 1
    • 最多只有一个顶点的入度与出度差为 1 1 1
    • 所有其他顶点的入度和出度相同

让我们考虑下面的这张图:
500
我们从起点 J F K JFK JFK 出发,合法路径有两条:

  • J F K → A A A → J F K → B B B → J F K JFK→AAA→JFK→BBB→JFK JFKAAAJFKBBBJFK
  • J F K → B B B → J F K → A A A → J F K JFK→BBB→JFK→AAA→JFK JFKBBBJFKAAAJFK

既然要求字典序最小,那么我们每次应该贪心地选择当前节点所连的节点中字典序最小的那一个,并将其入栈。最后栈中就保存了我们遍历的顺序。

为了保证我们能够快速找到当前节点所连的节点中字典序最小的那一个,我们可以使用优先队列存储当前节点所连到的点,每次我们 O ( 1 ) O(1) O(1) 地找到最小字典序的节点,并 O ( log ⁡ m ) O(\log m) O(logm) 地删除它。

然后我们考虑一种特殊情况:
500
当我们先访问 A A A AAA AAA 时,我们无法回到 J F K JFK JFK,这样我们就无法访问剩余的边了。也就是说,当我们贪心地选择字典序最小的节点前进时,我们可能先走入「死胡同」,从而导致无法遍历到其他还未访问的边。于是我们希望能够遍历完当前节点所连接的其他节点后再进入「死胡同」

注意对于每一个节点,它只有最多一个「死胡同」分支。依据前言中对于半欧拉图的描述,只有那个入度与出度差为 1 1 1 的节点会导致死胡同


解法 H i e r h o l z e r Hierholzer Hierholzer 算法

H i e r h o l z e r Hierholzer Hierholzer 算法用于在连通图中寻找欧拉路径,其流程如下:

  1. 从起点出发,进行深度优先搜索。
  2. 每次沿着某条边从某个顶点移动到另外一个顶点时,都需要删除这条边
  3. 如果没有可移动的路径,则将所在节点加入到栈中,并返回

当我们顺序地考虑该问题时,我们也许很难解决该问题,因为我们无法判断当前节点的哪一个分支是「死胡同」分支。不妨倒过来思考。我们注意到只有那个入度与出度差为 1 1 1 的节点会导致死胡同。而该节点必然是最后一个遍历到的节点——对于当前节点而言,走入它的每一个非「死胡同」分支进行深度优先搜索,都将会搜回到当前节点。而从它的「死胡同」分支出发进行深度优先搜索将不会搜回到当前节点。我们可以改变入栈的规则,当遍历完一个节点所连的所有节点后,才将该节点入栈(即逆序入栈),也就是说当前节点的死胡同分支始终优先于其他非「死胡同」分支入栈

这样就能保证我们可以「一笔画」地走完所有边,且最终的栈中逆序地保存了「一笔画」的结果。我们只要将栈中的内容反转,即可得到答案。

class Solution {
public:
    unordered_map<string, priority_queue<string, vector<string>, std::greater<string>>> vec;
    vector<string> stk;
    void dfs(const string& curr) {
        while (vec.count(curr) && vec[curr].size() > 0) {
            string tmp = vec[curr].top();
            vec[curr].pop();
            dfs(move(tmp));
        }
        stk.emplace_back(curr);
    }
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        for (auto& it : tickets) {
            vec[it[0]].emplace(it[1]);
        }
        dfs("JFK"); // JFK要么是欧拉回路的一点,要么是欧拉通路的起点 
        reverse(stk.begin(), stk.end());
        return stk;
    }
};

复杂度分析:

  • 时间复杂度: O ( m log ⁡ m ) O(m \log m) O(mlogm) ,其中 O ( m ) O(m) O(m) 是边的数量。对于每一条边我们需要 O ( log ⁡ m ) O(\log m) O(logm) 地删除它,最终的答案序列长度为 m + 1 m+1 m+1 ,而与 O ( n ) O(n) O(n) 无关。
  • 空间复杂度: O ( m ) O(m) O(m) ,其中 O ( m ) O(m) O(m) 是边的数量。我们需要存储每一条边。

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

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

相关文章

怎样获取某个文件的public方法个数

背景&#xff1a;idea 提供的list可以查看所有的构造方法&#xff0c;但是无法直接告诉我准确的数目&#xff0c;于是写了以下一个单独的类 import java.lang.reflect.Method; import java.lang.reflect.Modifier;public class MyPublicMethodCounter {public static void mai…

Cento7 Docker安装Zabbix,定制自定义模板

1.先安装docker环境 yum -y install yum-utils device-mapper-persistent-data lvm2#导入docker安装库 yum-config-manager \--add-repo \https://download.docker.com/linux/centos/docker-ce.repo #按指定版本安装好docker yum install docker-ce-20.10.5 docker-ce-cli-20…

vector类(顺序表)

文章目录 1.定义&#xff1a;接口成员函数构造成员函数析构函数赋值 2.迭代器2.1begin&#xff08;&#xff09;和end&#xff08;&#xff09;重点2.1.1应用2.1.1.1函数调用 2.1.1.2用变量接受迭代器 2.2rbegin()和rend()2.2.1应用 3.顺序表的访问&#xff08;增删查检&#x…

Vue的路由使用,Node.js下载安装及环境配置教程 (超级详细)

前言&#xff1a; 今天我们来讲解关于Vue的路由使用&#xff0c;Node.js下载安装及环境配置教程 一&#xff0c;Vue的路由使用 首先我们Vue的路由使用&#xff0c;必须要导入官方的依赖的。 BootCDN - Bootstrap 中文网开源项目免费 CDN 加速服务https://www.bootcdn.cn/ <…

架构核心技术之分布式消息队列

Java全能学习面试指南&#xff1a;https://javaxiaobear.cn 今天我们来学习分布式消息队列&#xff0c;分布式消息队列的知识结构如下图。 主要介绍以下内容&#xff1a; 同步架构和异步架构的区别。异步架构的主要组成部分&#xff1a;消息生产者、消息消费者、分布式消息队列…

Vue路由及Node.js环境搭建

一、Vue路由 1.1 定义 Vue路由是指使用Vue Router插件来管理前端应用程序的导航和页面路由的过程。它允许你在单页面应用程序&#xff08;SPA&#xff09;中定义不同的路由路径&#xff0c;并将每个路径映射到相应的组件。 通过使用Vue路由&#xff0c;你可以根据URL的变化加载…

无涯教程-JavaScript - ASIN函数

描述 ASIN函数返回给定数字的反正弦或反正弦,并返回以弧度表示的Angular,介于-π/2和π/2之间。 语法 ASIN (number)争论 Argument描述Required/OptionalNumberThe sine of the angle you want and must be from -1 to 1.Required Notes 如果您希望ASIN函数返回的Angular以…

HUAWEI华为荣耀猎人游戏本V700 i7独显2060(FRD-WFD9)原装出厂Windows10系统工厂模式(含F10还原)

华为HONOR荣耀笔记本原厂系统镜像包&#xff0c;安装恢复时自动创建F10一键智能还原功能 链接&#xff1a;https://pan.baidu.com/s/1_px_3Fr9qEE6jExz1eKKKg?pwdk6uc 提取码&#xff1a;k6uc 系统自带所有驱动、出厂主题壁纸LOGO、Office办公软件、华为电脑管家等预装程序…

基于springboot高校场馆预订系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

(并查集) 1971. 寻找图中是否存在路径 ——【Leetcode每日一题】

❓ 1971. 寻找图中是否存在路径 难度&#xff1a;简单 有一个具有 n 个顶点的 双向 图&#xff0c;其中每个顶点标记从 0 到 n - 1&#xff08;包含 0 和 n - 1&#xff09;。图中的边用一个二维整数数组 edges 表示&#xff0c;其中 edges[i] [ui, vi] 表示顶点 ui 和顶点 …

select in ()的时候,MySQL会自动按主键自增排序,要是按IN中给定的顺序来取,如何实现呢?

select * from table_name where id in ()的时候&#xff0c;MySQL会自动按主键自增排序&#xff0c;要是按IN中给定的顺序来取&#xff0c;如何实现呢&#xff1f; 比如下面这个查询结果&#xff0c;mysql会默认使用主键id的ASC自增排序结果集&#xff1a; 那么&#xff0c;…

RHEL 8.8 安装部署 Zabbix 6.4 详细过程

文章目录 前言1. 关闭系统防火墙2. 禁用 SELinux 模块3. 配置本地 YUM 源4. 配置 Zabbix 下载源5. 切换 PHP 模块版本6. 安装 Zabbix Server && Frontend && Agent7. 安装配置 MariaDB 数据库8. 为 Zabbix Server 配置数据库9. 启动 Zabbix Server 和 Agent 服…

【Java 基础篇】Java 中的 `wait` 与 `notify` 方法详解

在 Java 中&#xff0c;wait 与 notify 方法是用于线程之间通信的重要工具。它们被用于实现线程的等待与唤醒&#xff0c;以及线程之间的协作。本节将深入介绍这两个方法的使用方式、作用以及一些注意事项。 wait 方法 wait 方法是 java.lang.Object 类的一个实例方法&#x…

知识图谱(4)图算法

基于图有很多任务&#xff0c;比如&#xff1a; 节点分类&#xff1a;预测哪些网站是诈骗网站&#xff1b;关系预测&#xff1a;判断图中两个节点的关系&#xff1b;图分类&#xff1a;分子性质预测&#xff1b;聚类&#xff1a;社交网络分析&#xff0c;将相似用户聚类在一起…

如何选择合适的官文转录供应商

为什么质量不应该是唯一的考虑因素 官文记录必须准确无误——很多重要的程序&#xff08;包括法庭案件审判、严重欺诈调查和尸检调查&#xff09;成功得出结论&#xff0c;可能都依赖于记录的准确性。但是&#xff0c;在选择转录供应商时&#xff0c;还必须考虑更多因素。 官文…

2023Q2全球可穿戴腕带出货量达 4400 万台

全球可穿戴设备市场在2023年第二季度继续保持增长态势&#xff0c;总出货量达到了4400万台&#xff0c;同比增长了6%。这一增长得益于消费者对于可穿戴设备的需求不断增加&#xff0c;以及不同细分市场的需求反弹。 根据市场研究机构 Canalys 的最新报告&#xff0c;全球可穿戴…

阿里测开面试大全(一)附答案完整版

万字长文&#xff0c;建议收藏 1 什么是POM&#xff0c;为什么要使用它&#xff1f; POM是Page Object Model的简称&#xff0c;它是一种设计思想&#xff0c;而不是框架。大概的意思是&#xff0c;把一个一个页面&#xff0c;当做一个对象&#xff0c;页面的元素和元素之间操…

VM虚拟机CentOS7.9x64 LVM硬盘扩容

软件版本&#xff1a;VMWare Workstation14 虚拟机CentOS 7.9X64位 GParted 0.33.0 一、虚拟机安装gparted软件 sudo yum install epel-release sudo yum install gparted sudo yum install yum-utils git gnome-common gcc-c sudo yum-builddep gparted 二、关闭虚拟机&a…

【Java 基础篇】Java Condition 接口详解

Java 提供了一种更灵活和高级的线程协作机制&#xff0c;通过 Condition 接口的使用&#xff0c;你可以更精细地控制线程的等待和唤醒&#xff0c;实现更复杂的线程同步和通信。本文将详细介绍 Java 的 Condition 接口&#xff0c;包括它的基本概念、常见用法以及注意事项。 什…

TS编译器选项​compilerOptions指定编译后文件所在目录

compilerOptions是TS的编译器选项&#xff0c;主要在tsconfig.json文件中用于对ts编译为js文件时进行配置 "compilerOptions" : { 配置项 } 在tsconfig.json中编写如下代码&#xff1a; {// compilerOptions 编译器选项"compilerOptions": {// outDir 用于…