问题描述:小C和他的领导小F计划一次飞行,但由于严格的航空管制,他们的飞机仅能按特定的路线飞行:飞机只能飞往当前机场的相邻机场或相同航空公司管理的机场。为了减少起飞次数,小C需要制定最优的飞行路线。机场由 个数组airports标识,其中:
- 数组每个元素代表一个独特的机场,元素的值代表不同的航空公司。
- airports[0]为起点,airports[airports.length-1]为终点。
- 假设小C当前在机场i,那么i- 1和i+1(如果存在)代表邻近机场,飞机可以直接前往。
- 如果在机场i,且存在airports[i]== airports[j],则机场i和机场j同属一家航空公司,可直接飞往。
求最小起飞次数
1. 问题背景和需求
题目要求计算从起点机场到终点机场的最短飞行次数,其中飞行路线受到两个限制:
- 只能飞往相邻的机场(i-1 和 i+1)。
- 也可以飞往同一航空公司管理的其他机场。
目标是找到从起点机场(airports[0])到终点机场(airports[-1])的最小飞行次数。
2. 图建模
我们将机场视为图中的节点,飞行的路线视为图中的边。每个节点(机场)之间的边表示两机场之间有直接的航班。
- 如果机场
i和机场j是相邻的(i-1或i+1),那么有一条边连接i和j。 - 如果
i和j属于同一家航空公司,即airports[i] == airports[j],那么它们之间也有一条边,表示可以飞往同一航空公司管理的机场。
3. 算法选择
由于我们需要计算从起点机场到终点机场的最短路径,而每次飞行的“权重”是相同的(即每一次飞行的代价是 1),这个问题符合 无权图最短路径问题。最适合的算法是 广度优先搜索(BFS)。
BFS 有几个特点:
- BFS 保证在找到目标节点时,它是通过最短路径到达的。
- BFS 会逐层搜索,先探索距离起点最近的节点,确保首先到达终点的路径是最短的。
4. BFS 具体实现分析
BFS 的基本步骤如下:
-
初始化:
- 使用一个队列(
queue)存储待访问的节点。队列元素是一个二元组(当前机场索引, 当前飞行次数),表示从起点到当前机场所需要的飞行次数。 - 使用
visited集合记录已经访问过的机场,防止重复访问。
- 使用一个队列(
-
处理每个机场:
- 每次从队列中取出一个机场,检查是否是终点机场。如果是,直接返回飞行次数。
- 如果不是终点,则有两种方式可以“飞行”:
- 飞往相邻机场:如果
i-1或i+1是合法的并且未访问过,加入队列。 - 飞往同一航空公司管理的机场:查找所有与当前机场属于同一航空公司的其他机场,并且这些机场没有被访问过,加入队列。
- 飞往相邻机场:如果
-
终止条件:
- 如果队列为空且仍未找到终点,则说明无法到达终点,返回
-1。
- 如果队列为空且仍未找到终点,则说明无法到达终点,返回
-
优化点:
- 为了防止重复访问同一航空公司管理的机场,我们在每次访问完一个机场后,立即清空该航空公司的机场列表(
company_airports[airports[i]] = []),避免后续多次访问。
- 为了防止重复访问同一航空公司管理的机场,我们在每次访问完一个机场后,立即清空该航空公司的机场列表(
5. 时间复杂度分析
-
BFS的时间复杂度:BFS 中每个机场最多只会被访问一次,因此总的时间复杂度是
O(n),其中n是机场的数量。 -
查找同一航空公司管理的机场:对于每个机场,我们需要查找所有同一航空公司管理的机场,这个操作可以通过使用
defaultdict(list)来优化。每个航空公司最多涉及一次完整遍历,但由于我们在每次访问后清空航空公司列表,因此每个机场的所有操作都只会发生一次。 -
因此,总的时间复杂度仍然是
O(n)。
6. 空间复杂度分析
- 我们需要维护以下几个数据结构:
queue:存储待访问的机场,最多存储n个机场,因此空间复杂度为O(n)。visited:存储已访问的机场,最坏情况下需要存储n个机场,因此空间复杂度为O(n)。company_airports:存储每个航空公司管理的机场列表。最坏情况下,每个机场都属于不同的航空公司,因此也可能需要O(n)的空间。
因此,空间复杂度为 O(n)。
7. 特殊情况分析
- 只有一个机场:如果
n == 1,那么起点和终点是同一个机场,最小飞行次数为0。 - 无法到达终点:如果在搜索过程中,队列为空且未到达终点,返回
-1,表示无法从起点到达终点。
8. 代码实现再优化
在一些极端情况下,比如同一航空公司管理的机场很多,可能会导致同一航空公司重复搜索。为了避免这种情况,可以在每次使用航空公司列表之后,将它清空,确保不重复计算。
完整代码实现:
from collections import deque, defaultdict
def min_takeoffs(airports):
n = len(airports)
if n == 1:
return 0 # 如果只有一个机场,已经是起点和终点了,0次飞行
# 创建航空公司管理的机场映射
company_airports = defaultdict(list)
for i, company in enumerate(airports):
company_airports[company].append(i)
# BFS初始化
queue = deque([(0, 0)]) # (当前机场索引, 飞行次数)
visited = set([0]) # 已经访问过的机场
while queue:
i, takeoffs = queue.popleft()
# 如果到达终点机场,返回当前飞行次数
if i == n - 1:
return takeoffs
# 1. 尝试从相邻机场飞行
if i - 1 >= 0 and (i - 1) not in visited:
visited.add(i - 1)
queue.append((i - 1, takeoffs + 1))
if i + 1 < n and (i + 1) not in visited:
visited.add(i + 1)
queue.append((i + 1, takeoffs + 1))
# 2. 尝试飞往同一家航空公司管理的机场
for j in company_airports[airports[i]]:
if j != i and j not in visited:
visited.add(j)
queue.append((j, takeoffs + 1))
# 清空同一航空公司集合,防止重复访问
company_airports[airports[i]] = []
# 如果没有找到路径,返回-1(不可能到达终点)
return -1
# 测试
airports = [1, 2, 1, 3, 1]
print(min_takeoffs(airports)) # 输出应为最小飞行次数
总结
- 算法设计:使用 BFS 求解最短路径问题,确保每次访问机场时都能保证最少飞行次数。
- 优化:使用
defaultdict和visited集合来避免重复访问机场,并通过清空航空公司列表避免重复计算。 - 时间复杂度:
O(n),每个机场最多只会被访问一次,且每次访问的时间是常数级的。