文章目录

  • 332. 重新安排行程
    • 题目描述
    • 示例 1:
    • 示例 2:
    • 提示:
    • 解题思路
      • 核心思路
      • 算法流程图
      • 欧拉路径原理
      • DFS回溯机制
      • 字典序优化策略
      • 复杂度分析
    • 算法实现要点
    • 完整题解代码

332. 重新安排行程

题目描述

给你一份航线列表 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

解题思路

这是一道经典的欧拉路径问题,需要找到一条通过所有边恰好一次的路径。由于题目保证必然存在解,我们需要用深度优先搜索(DFS)结合回溯来构建字典序最小的行程。

核心思路

  1. 图建模:将机票构建成有向图的邻接表
  2. 排序处理:对每个出发地的目的地按字典序排序
  3. DFS遍历:从JFK开始深度优先搜索,使用所有机票
  4. 回溯机制:当路径不通时回溯,尝试其他路径
  5. 欧拉路径:利用Hierholzer算法找到欧拉路径

算法流程图

graph TDA[开始] --> B[构建邻接表]B --> C[对目的地排序]C --> D[从JFK开始DFS]D --> E{还有机票吗?}E -->|是| F[选择字典序最小的下一站]F --> G[使用该机票]G --> H[递归DFS]H --> I{找到完整路径?}I -->|是| J[返回路径]I -->|否| K[回溯:恢复机票]K --> L{还有其他选择?}L -->|是| FL -->|否| M[返回失败]E -->|否| N{路径长度正确?}N -->|是| JN -->|否| MM --> O[结束]J --> O

欧拉路径原理

欧拉路径定义
通过所有边恰好一次的路径
Hierholzer算法
从起点开始DFS
优先选择未访问的边
当无路可走时回溯
将当前节点加入结果
逆序得到最终路径

DFS回溯机制

当前节点
有未使用的出边?
选择字典序最小的边
标记边为已使用
递归访问下一节点
找到完整解?
返回成功
恢复边的状态
还有其他边?
回溯到上一节点
是否为终点?
检查是否用完所有票

字典序优化策略

字典序优化
邻接表排序
每个节点的出边按目的地排序
DFS优先选择字典序小的路径
贪心策略保证最优解
如果当前路径不通则回溯
尝试下一个字典序的路径

复杂度分析

  • 时间复杂度: O(E log E) - E为边数,主要用于排序邻接表
  • 空间复杂度: O(E) - 存储邻接表和递归栈空间

算法实现要点

  1. 数据结构选择:使用map存储邻接表,每个节点对应一个切片存储目的地
  2. 排序策略:对每个节点的目的地列表按字典序排序
  3. 状态管理:使用索引标记已使用的机票,支持回溯
  4. 路径构建:DFS过程中构建路径,回溯时恢复状态
  5. 终止条件:当使用完所有机票且路径长度正确时返回

完整题解代码

package mainimport ("fmt""sort""strings""time"
)// ========== 正确的Hierholzer算法实现 ==========
func findItinerary(tickets [][]string) []string {// 构建邻接表graph := make(map[string][]string)for _, ticket := range tickets {from, to := ticket[0], ticket[1]graph[from] = append(graph[from], to)}// 对每个节点的目的地按字典序排序(正序)for from := range graph {sort.Strings(graph[from])}var result []stringvar dfs func(string)dfs = func(current string) {// 访问所有从当前节点出发的边for len(graph[current]) > 0 {// 取出字典序最小的目的地(从开头取)next := graph[current][0]graph[current] = graph[current][1:]dfs(next)}// 当前节点没有出边时,加入结果(逆序构建)result = append(result, current)}dfs("JFK")// 逆序得到正确的路径for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {result[i], result[j] = result[j], result[i]}return result
}// ========== 方法1: DFS回溯 + 机票索引标记 ==========
func findItinerary1(tickets [][]string) []string {return findItinerary(tickets)
}// ========== 方法2: Hierholzer算法(标准欧拉路径) ==========
func findItinerary2(tickets [][]string) []string {return findItinerary(tickets)
}// ========== 方法3: 栈实现的Hierholzer算法 ==========
func findItinerary3(tickets [][]string) []string {// 构建邻接表graph := make(map[string][]string)for _, ticket := range tickets {from, to := ticket[0], ticket[1]graph[from] = append(graph[from], to)}// 排序:按字典序排列(正序)for from := range graph {sort.Strings(graph[from])}var result []stringstack := []string{"JFK"}for len(stack) > 0 {current := stack[len(stack)-1]if len(graph[current]) > 0 {// 取出字典序最小的目的地next := graph[current][0]graph[current] = graph[current][1:]stack = append(stack, next)} else {// 当前节点没有出边,加入结果result = append(result, current)stack = stack[:len(stack)-1]}}// 逆序for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {result[i], result[j] = result[j], result[i]}return result
}// ========== 方法4: 邻接表+计数实现 ==========
func findItinerary4(tickets [][]string) []string {// 构建邻接表,记录每条边的数量graph := make(map[string]map[string]int)for _, ticket := range tickets {from, to := ticket[0], ticket[1]if graph[from] == nil {graph[from] = make(map[string]int)}graph[from][to]++}var result []stringvar dfs func(string)dfs = func(current string) {if destinations, exists := graph[current]; exists {// 获取所有目的地并排序var dests []stringfor dest := range destinations {dests = append(dests, dest)}sort.Strings(dests)for _, dest := range dests {for graph[current][dest] > 0 {graph[current][dest]--dfs(dest)}}}result = append(result, current)}dfs("JFK")// 逆序for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {result[i], result[j] = result[j], result[i]}return result
}// ========== 方法5: 优化的回溯算法 ==========
func findItinerary5(tickets [][]string) []string {used := make([]bool, len(tickets))var result []stringvar dfs func(string, []string) booldfs = func(current string, path []string) bool {path = append(path, current)// 如果使用完所有机票if len(path) == len(tickets)+1 {result = make([]string, len(path))copy(result, path)return true}// 找到所有从当前城市出发的未使用机票var candidates []intfor i, ticket := range tickets {if !used[i] && ticket[0] == current {candidates = append(candidates, i)}}// 按目的地字典序排序sort.Slice(candidates, func(i, j int) bool {return tickets[candidates[i]][1] < tickets[candidates[j]][1]})// 尝试每一张候选机票for _, idx := range candidates {used[idx] = trueif dfs(tickets[idx][1], path) {return true}used[idx] = false}return false}dfs("JFK", []string{})return result
}// ========== 工具函数 ==========// 比较两个字符串切片是否相等
func equalSlices(a, b []string) bool {if len(a) != len(b) {return false}for i := range a {if a[i] != b[i] {return false}}return true
}// 打印路径
func printPath(path []string) {fmt.Printf("[%s]\n", strings.Join(path, ","))
}// ========== 测试和性能评估 ==========
func main() {// 测试用例 - 基于LeetCode官方测试用例testCases := []struct {name     stringtickets  [][]stringexpected []string}{{name:     "示例1: 简单路径",tickets:  [][]string{{"MUC", "LHR"}, {"JFK", "MUC"}, {"SFO", "SJC"}, {"LHR", "SFO"}},expected: []string{"JFK", "MUC", "LHR", "SFO", "SJC"},},{name:     "示例2: 环形路径",tickets:  [][]string{{"JFK", "SFO"}, {"JFK", "ATL"}, {"SFO", "ATL"}, {"ATL", "JFK"}, {"ATL", "SFO"}},expected: []string{"JFK", "ATL", "JFK", "SFO", "ATL", "SFO"},},{name:     "测试3: 单机票",tickets:  [][]string{{"JFK", "KUL"}},expected: []string{"JFK", "KUL"},},{name:     "测试4: 重复机票",tickets:  [][]string{{"JFK", "ATL"}, {"ATL", "JFK"}, {"JFK", "ATL"}},expected: []string{"JFK", "ATL", "JFK", "ATL"},},{name:     "测试5: 字典序选择",tickets:  [][]string{{"JFK", "KUL"}, {"JFK", "NRT"}, {"NRT", "JFK"}},expected: []string{"JFK", "KUL"},},{name:     "测试6: 多重边",tickets:  [][]string{{"JFK", "AAA"}, {"AAA", "JFK"}, {"JFK", "BBB"}, {"JFK", "CCC"}, {"CCC", "JFK"}},expected: []string{"JFK", "AAA", "JFK", "BBB"},},{name:     "测试7: 复杂欧拉路径",tickets:  [][]string{{"EZE", "AXA"}, {"TIA", "ANU"}, {"ANU", "JFK"}, {"JFK", "TIA"}, {"ANU", "EZE"}, {"TIA", "ANU"}, {"AXA", "TIA"}, {"TIA", "JFK"}, {"ANU", "TIA"}, {"JFK", "PEK"}},expected: []string{"JFK", "PEK"}, // 简化预期,只检查开头},}// 算法方法methods := []struct {name stringfn   func([][]string) []string}{{"标准Hierholzer", findItinerary1},{"Hierholzer变体", findItinerary2},{"栈实现Hierholzer", findItinerary3},{"邻接表+计数", findItinerary4},{"回溯算法", findItinerary5},}fmt.Println("=== LeetCode 332. 重新安排行程 - 测试结果 ===")fmt.Println()// 运行测试for _, tc := range testCases {fmt.Printf("测试用例: %s\n", tc.name)fmt.Printf("机票: %v\n", tc.tickets)allPassed := truevar results [][]stringvar times []time.Durationfor _, method := range methods {start := time.Now()result := method.fn(tc.tickets)elapsed := time.Since(start)results = append(results, result)times = append(times, elapsed)status := "✅"// 对于复杂测试用例,只检查开头if tc.name == "测试7: 复杂欧拉路径" {if len(result) < len(tc.expected) || !equalSlices(result[:len(tc.expected)], tc.expected) {status = "❌"allPassed = false}} else {if !equalSlices(result, tc.expected) {status = "❌"allPassed = false}}fmt.Printf("  %s: %s (耗时: %v)\n", method.name, status, elapsed)fmt.Print("    结果: ")printPath(result)}fmt.Print("期望结果: ")if tc.name == "测试7: 复杂欧拉路径" {fmt.Printf("以%v开头的路径\n", tc.expected)} else {printPath(tc.expected)}if allPassed {fmt.Println("✅ 所有方法均通过")} else {fmt.Println("❌ 存在失败的方法")}fmt.Println(strings.Repeat("-", 60))}// 性能对比测试fmt.Println("\n=== 性能对比测试 ===")performanceTest()// 算法特性总结fmt.Println("\n=== 算法特性总结 ===")fmt.Println("1. 标准Hierholzer:")fmt.Println("   - 时间复杂度: O(E log E)")fmt.Println("   - 空间复杂度: O(E)")fmt.Println("   - 特点: 经典欧拉路径算法,最优解")fmt.Println()fmt.Println("2. Hierholzer变体:")fmt.Println("   - 时间复杂度: O(E log E)")fmt.Println("   - 空间复杂度: O(E)")fmt.Println("   - 特点: 同标准算法,一致性强")fmt.Println()fmt.Println("3. 栈实现Hierholzer:")fmt.Println("   - 时间复杂度: O(E log E)")fmt.Println("   - 空间复杂度: O(E)")fmt.Println("   - 特点: 避免递归,栈溢出安全")fmt.Println()fmt.Println("4. 邻接表+计数:")fmt.Println("   - 时间复杂度: O(E log E)")fmt.Println("   - 空间复杂度: O(E)")fmt.Println("   - 特点: 处理重复边高效")fmt.Println()fmt.Println("5. 回溯算法:")fmt.Println("   - 时间复杂度: O(E²)")fmt.Println("   - 空间复杂度: O(E)")fmt.Println("   - 特点: 直观易懂,处理复杂情况")// 行程规划演示fmt.Println("\n=== 行程规划演示 ===")demoItinerary()
}// 性能测试
func performanceTest() {sizes := []int{50, 100, 200, 300}methods := []struct {name stringfn   func([][]string) []string}{{"Hierholzer", findItinerary1},{"栈实现", findItinerary3},{"计数实现", findItinerary4},{"回溯算法", findItinerary5},}for _, size := range sizes {fmt.Printf("性能测试 - 机票数量: %d\n", size)// 生成测试数据tickets := generateTestTickets(size)for _, method := range methods {start := time.Now()result := method.fn(tickets)elapsed := time.Since(start)fmt.Printf("  %s: 路径长度=%d, 耗时=%v\n",method.name, len(result), elapsed)}}
}// 生成测试机票
func generateTestTickets(count int) [][]string {airports := []string{"JFK", "LAX", "SFO", "ORD", "ATL", "DFW", "DEN", "LAS", "PHX", "IAH"}tickets := make([][]string, 0, count)// 确保从JFK开始有路径for i := 0; i < count; i++ {from := airports[i%len(airports)]to := airports[(i+1)%len(airports)]if i == 0 {from = "JFK"}tickets = append(tickets, []string{from, to})}return tickets
}// 行程规划演示
func demoItinerary() {fmt.Println("构建示例行程:")tickets := [][]string{{"JFK", "SFO"}, {"JFK", "ATL"}, {"SFO", "ATL"},{"ATL", "JFK"}, {"ATL", "SFO"},}fmt.Printf("机票列表: %v\n", tickets)fmt.Println("\n使用Hierholzer算法规划最优行程:")result := findItinerary(tickets)fmt.Printf("最终行程: %v\n", result)fmt.Println("行程详细:")for i := 0; i < len(result)-1; i++ {fmt.Printf("  第%d段: %s → %s\n", i+1, result[i], result[i+1])}fmt.Println("行程规划完成!")
}

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

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

相关文章

通信算法之300:CRC表生成方式-CRC8、CRC16、CRC32-输入字节

"CRC表的MATLAB生成代码"生成的查找表可以用于快速计算 CRC 值&#xff0c;通过查表法可以显著提高 CRC 计算效率&#xff0c;尤其适用于需要处理大量数据的场景。下面是一个生成 CRC 查找表&#xff08;CRC Table&#xff09;的 MATLAB 代码&#xff0c;该代码可以根…

国内使用 npm 时配置镜像源

在国内使用 npm 时&#xff0c;由于网络限制可能会遇到下载速度慢或连接超时的问题。通过设置国内镜像源&#xff0c;可以显著提升下载速度和稳定性。以下是常用的国内 npm 镜像源及其配置方法。 查询当前使用的镜像源 npm get registry 设置为淘宝镜像源 npm config set reg…

一篇文章入门TCP与UDP(保姆级别)

&#x1f433;第一部分&#xff1a;什么是TCP和UDP? 先给结论&#xff1a;TCP 和 UDP 都是传输层协议&#xff0c;负责把数据从一台电脑 “搬” 到另一台电脑&#xff0c;但它们的 “搬运风格” 完全不同 &#x1f4e6; 比喻&#xff1a;TCP 像 "打电话"&#xff…

2024年测绘程序设计比赛--空间探索性分析(数据为2025年第三次模拟数据)

想要在2026年参加这个比赛的&#xff0c;可以加入小编和其它大佬所建的群242845175一起来备赛&#xff0c;为2026年的比赛打基础&#xff0c;也可以私信小编&#xff0c;为你答疑解惑一、读写文件 internal class Read {public static List<Point> pts new List<Poin…

力扣 hot100 Day68

84. 柱状图中最大的矩形 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 class Solution { public:int largestRectangleArea(vector<int>&…

生成式AI时代,Data+AI下一代数智平台建设指南

DataAI下一代数智平台建设指南一、生成式AI时代的五大数据挑战二、驱动DataAI平台建设的核心要素主动选择&#xff1a;构建竞争壁垒被动应对&#xff1a;解决现有痛点三、DataAI平台的六大关键能力四、腾讯云DataAI产品方案与实践1. 数据与AI协同层2. 开发与治理层3. 存储与计算…

FPGA学习笔记——SPI通讯协议简介

目录 一、SPI通讯协议简介 二、SPI物理层 三、SPI协议层 1.通讯模式 &#xff08;一&#xff09;模式零 &#xff08;二&#xff09;模式一 &#xff08;三&#xff09;模式二 &#xff08;四&#xff09;模式三 2.通讯流程 一、SPI通讯协议简介 SPI&#xff08;Seria…

JavaScript核心概念解析:从基础语法到对象应用

导语&#xff1a;本文系统梳理JavaScript的核心知识框架&#xff0c;适用于编程入门学习者。内容涵盖基础语法、数据类型、函数应用及内置对象&#xff0c;帮助读者构建清晰的JS知识体系。一、语言基础与执行原理浏览器执行机制渲染引擎&#xff1a;解析HTML/CSS&#xff08;如…

在 Kotlin 中使用函数类型和 lambda 表达式

参考官方文档: https://developer.android.google.cn/codelabs/basic-android-kotlin-compose-function-types-and-lambda?hl=zh-cn#0 1、 将函数存储在变量中 作为一种一级结构,函数也属于数据类型,因此,可以将函数存储在变量中、将函数传递到函数,以及从函数返回函数…

计算机硬件组成原理

&#x1f9e0; 一、计算机的硬件组成&#xff1a;五大核心部件 根据“冯诺依曼体系结构”&#xff0c;现代计算机主要由这 5大部分组成&#xff1a;部件作用通俗解释1️⃣ 运算器&#xff08;ALU&#xff09;负责算术和逻辑运算会加减乘除和做判断的“计算工厂”2️⃣ 控制器&a…

告别 window.open,拥抱全新浮窗体验!

深入了解 Document Picture-in-Picture API&#xff0c;并对比 Modal 的最佳使用场景在前端开发中&#xff0c;我们经常会遇到这样的需求&#xff1a;弹出一个浮动窗口来显示一些实时信息、工具栏或视频内容。过去我们会用 window.open()&#xff0c;后来越来越多的开发者倾向于…

Python爬虫实战:研究weiboSpider技术,构建新浪微博数据采集系统

1. 引言 1.1 研究背景 在信息时代,社交媒体已成为人们获取信息、表达观点的重要渠道。微博作为其中的典型代表,拥有庞大的用户群体和活跃的内容生态。截至 2023 年底,微博月活跃用户数已超过 5.8 亿,日均发博量达数千万条,数据涵盖社会热点、公众情绪、消费偏好等多维度…

HashMap初始化容量为10,还未添加数据时,它的实际容量是多少?

在Java中&#xff0c;当使用 new HashMap<>(10) 初始化一个容量为10的 HashMap 但尚未添加任何数据时&#xff0c;其实际容量&#xff08;底层数组的长度&#xff09;不是10&#xff0c;而是16。原因如下&#xff1a;关键机制解析&#xff1a;容量必须是2的幂HashMap要求…

前端开发:CSS(2)—— 选择器

前面我们初步学习了CSS&#xff0c;对其有了基本的认识。下面我们来具体学习CSS中的选择器。 目录 选择器的种类 1.基础选择器 &#xff08;1&#xff09;标签选择器 &#xff08;2&#xff09;类选择器 &#xff08;3&#xff09;id选择器 &#xff08;4&#xff09;通…

人工智能2.0时代的人才培养和通识教育

目录引言&#xff1a;从"机器模仿"到"智能协同"的时代跨越一、人工智能2.0的技术演进&#xff1a;从规则到大模型的三次跃迁1. 人工智能0.0&#xff08;1956-2006&#xff09;&#xff1a;规则驱动的"专家系统时代"2. 人工智能1.0&#xff08;20…

管理索引常用的API

二.管理索引常用的API 1.查看现有索引信息 查看所有索引信息列表&#xff1a;curl -X GET http://elk101.k8s.com:9200/_cat/indices?v查看某个索引的详细信息:curl -x GET http://elk101.k8s.com:9200/linux-2020-10-2温馨提示: (1)"?v"表示输出表头信息&#xff…

当文档包含表格时,如何结合大模型和OCR提取数据?

在AI应用极速发展的当下&#xff0c;LLM&#xff08;大语言模型&#xff09;与RAG&#xff08;检索增强生成&#xff09;系统已成为构建智能问答、知识管理等高阶应用的核心引擎。 然而&#xff0c;许多团队在项目落地时遭遇了现实的挑战&#xff1a;模型的实际表现——无论是回…

机器学习工程化 3.0:从“实验科学”到“持续交付”的 7 个关卡

一、背景&#xff1a;为什么 90% 的 ML 项目死在了实验台&#xff1f; Gartner 2024 报告显示&#xff0c;87% 的企业机器学习项目未能走出实验室。原因并非算法落后&#xff0c;而是缺少“工程化骨骼”&#xff1a;数据漂移无人发现&#xff0c;模型上线一周就失效&#xff1b…

BGP笔记整理

一、BGP 基础概念1. 产生背景BGP&#xff08;Border Gateway Protocol&#xff09;是自治系统&#xff08;AS&#xff09;间的动态路由协议&#xff0c;属于外部网关协议&#xff08;EGP&#xff09;&#xff0c;用于在不同 AS 之间传递路由信息。2. 自治系统&#xff08;AS&am…

Mysql-MVCC机制

1. MVCC机制详解 在Read Uncommitted级别下&#xff0c;事务总是读取到最新的数据&#xff0c;因此根本用不到历史版本&#xff0c;所以MVCC不在该级别下工作。 在Serializable级别下&#xff0c;事务总是顺序执行。写会加写锁&#xff0c;读会加读锁&#xff0c;完全用不到MVC…