阿 D 的最佳飞行路线探索
问题描述
小 C 和他的领导小 F 计划一次飞行,但由于严格的航空管制,他们的飞机仅能按特定的路线飞行:
- 飞机只能飞往当前机场的相邻机场或相同航空公司管理的机场。
- 机场由一个数组
airports标识,其中:- 数组每个元素代表一个独特的机场,元素的值代表不同的航空公司。
airports[0]为起点,airports[airports.length - 1]为终点。- 假设小 C 当前在机场
i,那么i - 1和i + 1(如果存在)代表邻近机场,飞机可以直接前往。 - 如果在机场
i,且存在airports[i] == airports[j],则机场i和机场j同属一家航空公司,可直接飞往。
目标:求最小起飞次数。
测试样例
-
样例 1: 输入:airports = [10, 12, 13, 12, 14] 输出:3
-
样例 2: 输入:airports = [10, 11, 12, 13, 14] 输出:4
解题思路
1. 问题分析
这是一个典型的最短路径问题,需要在给定的约束条件下,从起点(airports[0])到达终点(airports[n - 1])用最少的步骤。
移动规则:
- 可以移动到相邻的机场(
i - 1或i + 1)。 - 可以直接飞往同一航空公司管理的任何机场(即
airports[i] == airports[j])。
2. 解法概述
使用 广度优先搜索(BFS) 算法:
- 节点:机场的索引位置。
- 边:
- 相邻机场之间的边(
i与i - 1、i + 1)。 - 同一航空公司机场之间的边(所有
airports[i] == airports[j])。
3. 算法步骤
-
构建航空公司映射:建立航空公司与其管理的机场列表的映射,便于快速找到同一航空公司的其他机场。
-
初始化 BFS:
- 使用队列
queue存储待访问的机场及其对应的起飞次数。 - 使用数组
visited标记机场是否已访问过,避免重复访问。
- 开始 BFS 遍历:
- 从起点开始,将其加入队列。
- 依次弹出队列中的机场,探索可以到达的下一个机场:
- 相邻机场:如果未访问过,加入队列。
- 同一航空公司的机场:如果未访问过,加入队列。
- 记录已访问的航空公司,避免重复处理同一航空公司的机场列表。
- 终止条件:
- 当到达终点机场时,返回当前的起飞次数。
- 如果队列为空且未到达终点,返回
-1(根据题目描述,不会发生)。
4. 示例分析
样例 1
airports = [10, 12, 13, 12, 14]
- 起点:索引 0,航空公司 10。
- 终点:索引 4,航空公司 14。
步骤:
-
第 0 步:
- 当前机场:0,起飞次数:0。
- 相邻机场:1(未访问),加入队列。
- 同一航空公司机场:无(航空公司 10 只有机场 0)。
-
第 1 步:
- 当前机场:1,起飞次数:1。
- 相邻机场:2(未访问),加入队列。
- 同一航空公司机场:3(
airports[1] == airports[3] == 12),加入队列。
-
第 2 步:
- 当前机场:2,起飞次数:2。
- 相邻机场:3(已访问),4(未访问),加入队列。
- 同一航空公司机场:无。
-
第 3 步:
- 当前机场:3,起飞次数:2。
- 已访问,不重复处理。
-
第 4 步:
- 当前机场:4,起飞次数:3。
- 到达终点,返回起飞次数 3。
代码详解
from collections import deque, defaultdict
def solution(airports):
n = len(airports)
if n == 1:
return 0 # 起点和终点相同,无需起飞
# 构建航空公司所属机场的映射
airline_map = defaultdict(list)
for i, company in enumerate(airports):
airline_map[company].append(i)
# BFS 队列,存储 (当前机场索引, 当前起飞次数)
queue = deque([(0, 0)])
visited = [False] * n # 记录机场是否访问过
visited[0] = True # 起点已访问
visited_airlines = set() # 记录已访问的航空公司,避免重复处理
while queue:
current, flights = queue.popleft()
# 如果到达终点,返回起飞次数
if current == n - 1:
return flights
# 考虑相邻机场
for neighbor in [current - 1, current + 1]:
if 0 <= neighbor < n and not visited[neighbor]:
visited[neighbor] = True
queue.append((neighbor, flights + 1))
# 考虑同一航空公司的机场
company = airports[current]
if company not in visited_airlines:
visited_airlines.add(company)
for neighbor in airline_map[company]:
if not visited[neighbor]:
visited[neighbor] = True
queue.append((neighbor, flights + 1))
return -1 # 如果无法到达终点,返回 -1(实际不会发生)
代码解释
-
导入模块:
deque:高效的队列,实现 BFS。defaultdict:默认字典,用于存储航空公司映射。
-
函数定义:
def solution(airports): -
初始化:
n:机场数量。- 特例处理:如果只有一个机场,返回 0。
-
构建航空公司映射:
python 复制代码 airline_map = defaultdict(list) for i, company in enumerate(airports): airline_map[company].append(i)airline_map:键为航空公司,值为该航空公司管理的机场索引列表。
-
初始化 BFS 队列和访问数组:
python 复制代码 queue = deque([(0, 0)]) visited = [False] * n visited[0] = True visited_airlines = set()queue:存储待访问的机场及其起飞次数。visited:标记机场是否已访问,防止重复访问。visited_airlines:记录已处理过的航空公司,避免重复遍历同一航空公司的机场列表。
-
BFS 主循环:
python 复制代码 while queue: current, flights = queue.popleft()- 弹出队首元素,获取当前机场和起飞次数。
-
检查是否到达终点:
python 复制代码 if current == n - 1: return flights -
探索相邻机场:
python 复制代码 for neighbor in [current - 1, current + 1]: if 0 <= neighbor < n and not visited[neighbor]: visited[neighbor] = True queue.append((neighbor, flights + 1))- 检查左邻和右邻机场是否存在且未访问,加入队列。
-
探索同一航空公司的机场:
python 复制代码 company = airports[current] if company not in visited_airlines: visited_airlines.add(company) for neighbor in airline_map[company]: if not visited[neighbor]: visited[neighbor] = True queue.append((neighbor, flights + 1))- 如果当前航空公司未处理过,遍历该航空公司管理的所有机场,若未访问过,加入队列。
-
返回结果:
- 如果遍历结束仍未到达终点,返回
-1(根据题目描述,不会发生)。
- 如果遍历结束仍未到达终点,返回
知识总结
1. 广度优先搜索(BFS)
-
定义:一种遍历或搜索数据结构(如树或图)的算法。
-
特点:
- 使用队列实现,先入先出。
- 按照层次逐层遍历,先访问离起点最近的节点。
2. 图的表示与遍历
- 邻接表:用列表或字典表示图中节点的相邻关系。
- 访问标记:使用数组或集合记录已访问的节点,避免重复遍历。
3. defaultdict 的使用
- 作用:当字典的键不存在时,自动为其创建默认值,避免
KeyError。 - 应用:在构建航空公司映射时,便于添加机场索引列表。
4. 算法优化
-
避免重复处理:
- 对已访问的机场和航空公司进行标记,减少不必要的计算。
-
时间复杂度:
- 因为每个机场和航空公司最多访问一次,时间复杂度为
O(n)。
- 因为每个机场和航空公司最多访问一次,时间复杂度为
学习建议
对于初学者
-
理解 BFS 的核心思想:掌握 BFS 的实现方式和适用场景,对于解决最短路径问题非常有用。
-
熟练使用数据结构:
- 队列:了解如何使用
deque实现队列操作。 - 字典和集合:掌握
defaultdict和集合的用法,提高代码的简洁性和效率。
- 队列:了解如何使用
-
练习图的遍历和搜索:
- 多做一些图相关的算法题,熟悉不同的图表示方法和遍历技巧。