LeetCode 1436 旅行终点站:哈希集合与图论入度出度解析

发布时间:2026/10/16 15:52:11

LeetCode 1436 旅行终点站:哈希集合与图论入度出度解析 1. 先看题目这条旅行线路到底在问什么第187场周赛的T1也就是编号1436的这道“旅行终点站”我印象还挺深。倒不是因为它难恰恰是因为它太简单了简单到很多人反而在它上面翻了车。LeetCode的周赛从T1开始就是抢时间的T1拖得越久后面三道题的心态崩得越快。而这道题就是典型的“会者不难难者不会”的代表。题目描述很直白给你一份旅游线路图用数组 paths 表示其中 paths[i] [cityAi, cityBi] 表示线路从 cityAi 直接前往 cityBi。要求找出这次旅行的终点站也就是没有任何可以通往其他城市的线路的那个城市。换句话说给定若干条有向边找出那个“只进不出”的节点。其实这就是一道有向图的基本功题目。虽然披着旅行线路的外衣但本质就是给你一堆 A-B 的边让你找出没有出边的那一个节点。文中出现的城市名是小写字母组成的字符串长度在1到10之间路径数量最多100条城市数量不会超过路径数加一。数据范围很小暴力解法甚至也能通过但优秀的解法应该做到 O(N) 时间、O(N) 空间。这道题适合谁去看呢一个是准备参加周赛、想在T1上稳住节奏的选手另一个是刚开始刷图论、还分不清入度和出度概念的初学者。哪怕你现在连“哈希集合”是什么都不太清楚读完这篇也能把这道题彻底吃透顺便建立起一套处理“找唯一特殊节点”类问题的思维模型。2. 核心思路终点站的本质是“没有出度”2.1 用图的角度看问题而不是死磕城市名拿到这题第一步要做的是把描述“翻译”成数据结构语言。paths 数组里每一项是 [A, B]意思就是有一条从 A 指向 B 的有向边。把所有城市抽象成节点所有线路抽象成有向边题目就变成了在一张有向图里找一个出度为0的节点。这个转换很重要因为一旦你把这个模型建立起来题目就基本解完了。什么叫出度就是一个节点作为起点出现的次数。终点站的定义是“没有任何可以通往其他城市的线路”翻译成图论语言就是“没有一个路径以它作为起点”换句话说它的出度是0。但是要注意一个细节终点站一定出现在某个路径的终点位置也就是说它的入度至少是1。这两个条件加在一起才是完整的判定标准一个城市如果从来没有作为起点出现过但作为终点出现过那它就是答案。只看“出度为0”还不够因为如果有一个城市从来没出现过它的出度也是0但它显然不是终点站。所以正确的判定方式是在 paths 里找到一个 cityBi它不在任何 cityAi 的集合里。2.2 三种解法从暴力到最优先说最暴力的思路遍历所有路径的终点对每个终点再去遍历一遍所有路径的起点看看它有没有“露脸”。这需要用两层循环时间复杂度 O(N^2)。题目里路径数量最多100所以哪怕用这个笨办法也能过但显然它不是我们在周赛里应该写出来的代码——T1讲究的是又快又稳O(N^2)虽然能过但没必要。稍微进阶一点的思路是统计每个城市的出度。用一个哈希表遍历所有 paths把每个 path[0] 的计数加一最后再遍历一次 paths找到 path[1] 的出度为0的那个城市。这个思路是对的复杂度降到了 O(N)空间 O(N)。最推荐的其实是基于哈希集合的写法先把所有起点放进一个 HashSet然后再遍历所有终点看哪个终点不在这个集合里它就是答案。这个写法比出度统计更简洁逻辑也更直接。它本质上利用了“终点站一定不在起点集合里”这个必要条件。整个过程只需要一遍遍历放起点一遍遍历找终点时间复杂度 O(N)空间复杂度 O(N)。2.3 复杂度与适用场景对比三种方法的对比可以这样看方法时间复杂度空间复杂度代码量推荐程度暴力双重循环O(N^2)O(1)很少不推荐哈希表统计出度O(N)O(N)中等可选哈希集合找不在起点中的终点O(N)O(N)最少强烈推荐你可能会问出度统计和哈希集合法看起来差不多为什么要选后者原因在于哈希集合法省去了“计数”这个中间状态。统计出度要维护每个城市的出现次数如果你只需要找“最后剩下的那个节点”用集合天然去重的特性反而更贴合题意。而且集合版的代码短写起来不容易出错在周赛的环境下代码越短出bug的概率越低。3. 多语言实现把思路写成不会出错的代码3.1 Python实现与细节Python 的代码可以写得非常紧凑。先把所有起点收集到一个集合里再找不在集合中的终点class Solution: def destCity(self, paths: List[List[str]]) - str: starts set() for path in paths: starts.add(path[0]) for path in paths: if path[1] not in starts: return path[1]如果你喜欢更 Pythonic 的风格也可以用集合推导式和 next 表达式一行搞定核心逻辑class Solution: def destCity(self, paths: List[List[str]]) - str: starts {path[0] for path in paths} return next(path[1] for path in paths if path[1] not in starts)这里面有几个值得注意的细节。第一Python 的 set 是无序且去重的你不需要担心里面出现重复的起点。第二next 函数在生成器找不到元素时会抛出 StopIteration 异常但题目保证一定存在唯一的终点站所以这里不会触发。第三如果你用的是 Python 老版本List 的 typing 注解要记得从 typing 模块导入但在现代 LeetCode 环境里直接用 list 也可以。我看到有些人会图省事直接统计所有城市出现的次数返回出现次数为1的城市。这个写法在大部分测试用例上都能过但它是错的后面我会专门讲这个坑。3.2 C和Java写法示例C 的思路完全一样用 unordered_set 存起点class Solution { public: string destCity(vectorvectorstring paths) { unordered_setstring starts; for (auto p : paths) { starts.insert(p[0]); } for (auto p : paths) { if (starts.find(p[1]) starts.end()) { return p[1]; } } return ; } };Java 的写法则要稍微注意一下泛型和 List 的取值方式class Solution { public String destCity(ListListString paths) { SetString starts new HashSet(); for (ListString p : paths) { starts.add(p.get(0)); } for (ListString p : paths) { if (!starts.contains(p.get(1))) { return p.get(1); } } return ; } }C 里 unordered_set 的 find 和 end 比较是标准操作要注意别把 insert 后的返回值弄混了。Java 里 HashSet 的 contains 方法时间复杂度是 O(1)所以整体思路和 Python 版完全一致。三个语言用的是同一套逻辑这就说明你真正需要掌握的是“把起点放入集合再从终点里找答案”这个模型而不是死记某一种语言的语法。3.3 边界条件和代码健壮性写完之后建议自己在脑子里跑两个极端情况。第一个是 paths 只有一条路线比如 [[A,B]]这时起点集合是 {A}终点 B 不在集合中直接返回 B没有任何问题。第二个是所有城市组成一条长链比如 [[A,B],[B,C],[C,D]]起点集合是 {A,B,C}终点集合是 {B,C,D}只有 D 不在起点集合里正确。还有一个小问题如果 paths 里出现了空数组怎么办题目描述中 paths[i] 是长度为2的数组但数据范围那里写的 0 paths[i].length 100 会让一些人多虑。在实际测试用例中paths[i] 一定是合法的一对城市名不需要额外判空。不过你要是想写得特别稳也可以在遍历时加上长度判断但这属于防御性编程周赛里不建议花这个时间。4. 实战复盘周赛里这道题最容易踩的坑4.1 常见错误与反例这道题最大的坑就是“统计所有城市出现次数返回只出现一次的城市”。很多新手会踩进去我当初第一次做这题也差点这么干。给你一个反例就明白了paths [[A,B],[C,A],[D,C]]这条线路是 D-C-A-B终点是 B。现在统计所有城市出现次数A 出现2次C 出现2次D 出现1次B 出现1次。如果按照“只出现一次”来判定D 和 B 都是候选答案但 D 是起点根本不是终点站。所以只看出现次数完全不靠谱必须区分一个城市是“作为起点出现”还是“作为终点出现”。另一个容易犯的错误是把思路搞反去找“不在任何终点里的起点”也就是想找起点站。注意题目问的是终点站不是起点站。虽然这两个问题对称但一不留神就会写反。找起点的做法是把所有终点放进集合然后返回不在集合中的起点。这道题的正确答案是反过来把所有起点放进集合返回不在集合中的终点。我见过有群友赛后复盘说自己的代码逻辑看起来完全没问题结果一提交就 WA仔细一看就是把 start 和 dest 的集合搞反了。4.2 周赛抢分策略周赛的 T1 通常是最简单的题目目标是在极短时间内 AC。根据我的经验这题的理想时间线是这样的读题30秒建模30秒写代码30秒跑示例验证20秒总用时控制在2分钟以内。如果你在 T1 上花了超过5分钟说明你需要加强“把文字描述翻译成数据结构”的练习。很多人一看到图论、路线、城市这些词就开始脑补复杂的DFS或拓扑排序。实际上你只要瞄一眼数据范围——paths 长度最多100——就该知道 T1 考的不是算法复杂度而是基本建模能力。在 T1 上使用 DFS 或者建邻接表属于典型的杀鸡用牛刀。不是说不行而是你花了更多时间写复杂代码反而增加了出错的概率。我的建议是周赛开始后先把四道题都扫一眼T1 直接按“哈希集合”套路秒掉把节约下来的时间留给后面的 T2 和 T3。因为周赛排名很大程度上取决于完成时间T1 的 AC 时间每慢一分钟排名都会往后掉一大截。4.3 快速自查清单做完这道题之后我会在提交前默念几个检查点是否把所有起点都加入了集合有没有漏掉最后一条路径返回的是终点而不是起点判断条件是不是path[1] not in starts如果 paths 长度为1代码能否正常返回唯一终点如果存在 A-B 和 B-A 这样的循环结果会怎样这个输入违反了题目“唯一终点”的约束所以不需要额外处理但你可以想想自己的代码在这种情况下会返回什么有助于加深理解。这个自查过程只需要10秒但对避免低级失误非常有效。特别是第四条我建议你多想想它不违反题意但能帮你理解出度和入度的关系。5. 从这道题延伸出去的图论思维5.1 入度出度模型在其他题目中的应用做完这道题如果只是记住“用set找终点”那就太可惜了。这道题背后藏着的是图论里非常基础也非常重要的入度出度模型。所谓入度就是有多少条边指向这个节点出度就是有多少条边从这个节点指出。很多看起来完全不同的题目最后都能归结为找入度或出度满足特定条件的节点。LeetCode 里非常出名的一道题——找小镇法官就是同一个模型。题目描述是小镇里有 N 个人法官不相信任何人所有人都信任法官。翻译成图论语言就是法官的入度是 N-1出度是0。你需要找到那个唯一同时满足这两个条件的节点。你看这和本道题“终点站入度至少1、出度为0”的判定方式何其相似。再比如拓扑排序中找入度为0的节点作为起点本质上也是在玩入度出度这套东西。所以当你把“旅行终点站”这道题吃透再去刷其它图论入门题你会发现自己能很快地辨认出题目背后的核心考点而不是被表面的故事包装迷惑。5.2 面试和工程中怎么迁移这套思路面试的时候这道题可以有很多变体。比如给你一份任务依赖列表每个任务依赖另一个任务让你找出最后的那个任务或者给你一组“先修课程”的二元组让你判断学习顺序。这些题目的本质都是图上的入度出度分析。工程上也有类似的场景。我实际开发中遇到过这样一个需求有一组服务调用链记录每条记录是 [调用方, 被调用方]需要找出整个调用链最末端那个服务。这不就是旅行终点站的翻版吗只不过把城市名换成了服务名把旅行线路换成了调用关系。这时候哈希集合的解法可以直接迁移过来连算法都不用改。所以我觉得做算法题的最高境界不是背题而是建立“题目特征到算法模型”的映射。看到“给一堆二元组找唯一特殊节点”脑子里立刻跳出“集合差值入度出度哈希表”这几个关键词那你刷这道题的时间就没有白费。这题给我最大的启发是简单题里往往藏着最常用的图论思维。不要因为 AC 了就急着做下一题停下来想一想为什么哈希集合能一步定位终点想一想如果路径再多100倍这题还能不能这么写。把这些想明白下次遇到 T1你就能做到看到题目的那一刻已经知道代码要怎么写了。
延伸阅读

更多相关文章

2026/10/16 15:52:11

NOIP普及组模拟赛复盘:从DP到并查集的算法补强指南

5月2日的NOIP普及组模拟赛,编号C组31,是我在训练平台上参加的一场相当有参考价值的摸底。赛前一周我刻意压缩了训练量,只做了模拟题和简单DP的专题,为的是把基本分拿稳;赛后才意识到,这种“稳”其实掩盖了一…

2026/10/16 15:52:11

服务器运行报告模板:三层巡检记录与故障避坑指南

简介:服务器运行报告模板是一份专为IT运维人员与系统管理员设计的标准化巡检文档,用于规范记录服务器设备信息、硬件状态、操作系统及应用系统运行情况,帮助团队在执行例行维护时快速定位隐患、形成统一检查口径。包内仅含1个doc文件&#xf…

2026/10/16 15:47:10

Cityscapes语义分割数据加载避坑指南

简介:本资源为Cityscapes数据集的首个分卷(一),专为计算机视觉研究者、自动驾驶算法工程师及深度学习初学者设计,聚焦城市街景语义分割任务,支撑模型训练、泛化能力验证与IoU等核心指标评估。压缩包含2000个…

2026/10/16 16:58:00

博图V15.1单步六层电梯PLC程序框架与调试详解

简介:基于博图V15.1开发的单步六层电梯控制程序,面向PLC自动化学习者与现场调试人员,覆盖复位、启动、多层顺次响应、反向呼叫无效、每层4秒运行与4秒停留等控制逻辑,层次完整,适合作为中高级编程练习或教学案例。资源…

2026/10/16 16:58:00

BERT面试深度拆解:从架构原理到微调避坑实战指南

1. 为什么BERT值得花时间深挖如果你最近在准备算法岗面试,或者想从传统机器学习转向NLP方向,大概率绕不开BERT这个模型。面试官问BERT,不是想听你背“Bidirectional Encoder Representations from Transformers”这个全称,而是想通…

2026/10/16 16:58:00

云原生学习路线图:分层能力地图与实战跃迁指南

简介:本资源是一份系统化、分层级的云原生技术学习路线图,面向开发者、运维工程师及希望转型云原生领域的IT从业者,解决技术栈庞杂、学习路径不清晰、各组件关系难把握等核心痛点。路线图PDF文件共1个,大小1.29MB,内容…

2026/10/16 16:58:00

从信令风暴到Agent协作:多Agent系统防崩的七条设计原则

1. 从一场信令风暴说起:所有设备都在“正确工作”,系统却崩了几年前我还在做通信网络维护时,经历过一次真正让我失眠的故障。节假日的业务高峰,话务量比平时翻了好几倍,核心网突然开始大面积丢消息。我盯着监控大屏&am…

2026/10/16 16:58:00

Qwen3-TTS深度体验:MLLM语音合成原理、部署与调参实战

最近做语音相关的东西比较多,试了不少TTS方案,Listed语音大概排到某一轮的时候,我看到了Qwen3-TTS这个名字。第一反应是:“又来一个套壳模型?”但实际跑了几个样例之后,确实感觉到这类用多模态大语言模型&a…

2026/10/16 16:52:59

二叉树基础与遍历全解:真/满/完全二叉树判定及递归迭代实现

数组、链表、栈、队列这些线性结构,我相信很多人学完以后觉得自己"数据结构入门了"。可真到了面试或者实际项目里,遇到树形结构相关的题目,大部分人立马就露馅了。树是第一种真正意义上"非线性"的数据结构,也…

2026/10/16 5:33:43

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/16 4:03:48

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/15 13:42:16

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/16 0:01:11

Cephadm实战指南:Linux系统工程师的生产级Ceph集群部署与排障

简介:本资源是一份完整的Ceph分布式存储系统中文技术文档合集,面向Linux系统运维工程师、云平台架构师及存储方向开发者,帮助读者系统掌握Ceph集群的部署、配置与运维核心能力。文档覆盖对象存储(RGW)、块设备&#xf…

2026/10/16 0:01:11

ITK中SpatialObject几何体系解析:坐标系、变换与管道集成

1. 空间对象的定位:为什么 ITK 需要一套几何描述体系很多刚开始接触 ITK 的朋友,包括我自己,最初都是被它的图像滤波、配准、分割这些“正统”功能吸引进来的。等真正上手做点实际项目,比如把分割出来的器官边界做量化分析、给血管…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑