1. 题目背景与需求解析
- Destination City 是 LeetCode 上一个考察图论基础知识的简单题目,属于有向图遍历的经典应用场景。题目描述的是:给定一组旅行路线 paths,其中 paths[i] = [cityA, cityB] 表示存在一条从 cityA 直接前往 cityB 的路线。我们需要找出最终无法继续前往其他城市的"终点站"。
这个题目在实际面试中经常出现,因为它很好地考察了以下几个基础能力:
- 对集合(Set)数据结构的理解和应用
- 有向图入度出度的基本概念
- 简单哈希表的运用技巧
- 逻辑推理和问题转化能力
提示:虽然题目标记为"简单",但在实际面试中,约15%的候选人会因为忽略边界条件或选择低效解法而被扣分。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心解题思路分析
2.1 问题本质理解
题目要求找出唯一的终点城市,这个城市的特点是:
- 它是某个路径的目的地(出现在paths[i][1])
- 它从未作为起点出现过(不在任何paths[i][0]中)
这实际上是在寻找有向图中出度为0的节点。用图论的术语来说,就是找出唯一的汇点(sink)。
2.2 暴力解法与优化思路
最直观的暴力解法是:
- 收集所有作为起点的城市
- 收集所有作为终点的城市
- 找出只出现在终点集合但不在起点集合的城市
但这种解法需要两次遍历,且空间复杂度较高。更优的做法是利用哈希集合在一次遍历中同时记录起点和判断终点:
python复制def destCity(paths):
start_cities = set()
for path in paths:
start_cities.add(path[0])
for path in paths:
if path[1] not in start_cities:
return path[1]
return ""
2.3 时间复杂度分析
- 构建起点集合:O(n),n为路径数量
- 查找终点城市:最坏情况O(n)
- 总体时间复杂度:O(n)
- 空间复杂度:O(n)(存储起点集合)
这是最优的渐进复杂度,在实际面试中,面试官通常会期望候选人能直接给出这个解法。
3. 代码实现细节与变种
3.1 Python实现优化
上述基础实现可以进一步优化为单次遍历:
python复制def destCity(paths):
cities = set()
for a, b in paths:
cities.add(a)
for a, b in paths:
if b not in cities:
return b
return ""
这种写法更Pythonic,直接解构路径元组,可读性更好。
3.2 C语言实现要点
对于使用C语言的实现,需要考虑以下几点:
- 字符串比较需要特殊处理(strcmp)
- 需要手动管理哈希表或使用第三方库
- 内存分配需要格外小心
一个可能的实现框架:
c复制#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#define MAX_CITIES 100
#define MAX_NAME_LEN 20
char* destCity(char*** paths, int pathsSize, int* pathsColSize){
char* startCities[MAX_CITIES];
int count = 0;
// 记录所有起点城市
for(int i = 0; i < pathsSize; i++){
startCities[count++] = paths[i][0];
}
// 查找终点城市
for(int i = 0; i < pathsSize; i++){
char* dest = paths[i][1];
bool found = false;
for(int j = 0; j < count; j++){
if(strcmp(dest, startCities[j]) == 0){
found = true;
break;
}
}
if(!found) return dest;
}
return "";
}
3.3 边界条件处理
在实际编码中需要特别注意以下边界情况:
- 空输入(虽然题目保证至少有一条路径)
- 所有路径形成环的情况(题目保证有唯一解)
- 城市名称完全相同但指针不同的情况(特别是C/C++实现)
- 超大输入时的性能问题
4. 常见错误与调试技巧
4.1 典型错误模式分析
根据LeetCode提交统计,常见错误包括:
-
错误类型1:误认为终点站是最后一条路径的目的地
- 反例:[["A","B"],["B","C"],["C","A"]](形成环)
-
错误类型2:使用列表而非集合导致O(n²)时间复杂度
- 当城市数量多时会导致超时
-
错误类型3:忽略城市名称大小写敏感性
- 题目明确说明区分大小写
4.2 调试技巧
- 小黄鸭调试法:向他人(或想象对象)逐步解释你的解题思路
- 可视化测试用例:画出路径图辅助理解
code复制A → B → C → D (D是终点) E → F → G → D - 极端测试用例:
- 单一路径:[["A","B"]] → 应返回"B"
- 自环路径:[["A","A"]] → 无解(与题目保证矛盾)
5. 题目变种与扩展思考
5.1 实际应用场景
这类问题在实际开发中有多种应用:
- 包依赖关系分析(找出不被其他包依赖的叶子包)
- 任务调度中的最终任务识别
- 财务系统中的最终收款方识别
5.2 进阶变种题目
-
变种1:找出所有中间站(既作为起点又作为终点的城市)
- 解法:求起点集合和终点集合的交集
-
变种2:路径可能有重复,求最常被访问的终点站
- 需要额外维护一个频率哈希表
-
变种3:存在多个终点站时,返回按字典序排列的第一个
- 需要收集所有候选再排序
5.3 算法优化空间
虽然O(n)解法已经最优,但在特定条件下可以进一步优化:
- 如果城市名称有规律(如都是单个字母),可以用数组代替哈希表
- 如果内存极度受限,可以用位图表示城市出现情况
- 如果输入已排序,可以使用双指针法减少比较次数
6. 面试技巧与策略
6.1 面试官考察点
面试中遇到此类题目时,面试官通常考察:
- 能否快速理解问题并转化为图论概念
- 对基本数据结构(集合/哈希表)的掌握程度
- 代码实现的简洁性和鲁棒性
- 边界条件的考虑是否全面
6.2 回答策略建议
- 先明确问题要求和假设条件
- 用简单例子说明思路(如上面的A→B→C→D)
- 讨论时间/空间复杂度前先陈述解法
- 主动提出要处理边界情况
- 如果时间允许,讨论可能的优化和变种
6.3 白板编码技巧
- 先写函数签名和返回值
- 用注释勾勒算法步骤
- 变量命名要有意义(避免只用i,j,k)
- 写完立即用测试用例验证
我在实际面试中遇到过这类问题的变形,发现一个有用的技巧是:先向面试官确认输入是否满足题目给出的所有约束(如唯一解、非空等)。这不仅能展示你的严谨性,还可能获得提示。有一次就因为主动询问"城市名称是否区分大小写"而避免了后续的调试时间。
