题目解析
-
思路:
-
首先,明确这是一个求最优路径相关的问题,需要找到从起点(数组
airports的第一个元素对应的机场)到终点(数组airports的最后一个元素对应的机场)的最少起飞次数,也就是最小步数,很适合用广度优先搜索(BFS)算法来解决。 -
初始化一些必要的数据结构和变量。创建一个队列
queue(这里使用LinkedList实现)用于 BFS 过程中存储待访问的机场索引,一个布尔数组visited用于标记各个机场是否已经被访问过,避免重复访问,还创建了一个HashMap类型的airlineMap,用于记录每个航空公司所管理的机场索引列表,方便后续查找同属一家航空公司的其他机场。 -
通过循环遍历
airports数组,将每个航空公司对应的机场索引添加到airlineMap中,构建起航空公司与旗下机场的对应关系。 -
接着将起点机场索引
0加入队列queue,并标记起点已访问visited[0] = true,同时初始化起飞次数(步数)计数器steps为0。 -
进入 BFS 的主循环,只要队列
queue不为空,就进行一轮搜索。在每一轮中,先获取当前队列的大小size,这代表当前层的节点数量(在本题中就是同一轮可到达的机场数量),然后通过循环遍历这一层的节点(即当前可到达的各个机场):- 取出队列头部的机场索引
current,代表当前所在机场。 - 首先判断是否到达终点(
current == n - 1,n是airports数组长度),如果到达则直接返回当前的起飞次数steps。 - 接着考虑相邻机场,如果当前机场索引减
1后合法(不越界且未被访问过),则将其标记为已访问,并加入队列,同理对于当前机场索引加1后的机场也做相同操作,这样就可以探索相邻机场这条飞行规则。 - 再查找与当前机场同属一家航空公司的其他机场,通过
airlineMap获取同航空公司的机场索引列表sameAirlineAirports,遍历这个列表,如果某个同航空公司的机场未被访问过,就将其标记为已访问并加入队列,同时为了避免后续重复访问这些同航空公司的机场(因为同属一家航空公司的机场之间是相互可达的,一次访问即可),将当前航空公司的记录从airlineMap中移除。
- 取出队列头部的机场索引
-
每完成一层(一轮)的搜索,也就是遍历完当前队列中的所有机场后,起飞次数
steps加1,代表需要多一次起飞来进入下一轮探索的机场。如果最终循环结束还未到达终点,说明无法到达,返回-1(理论上按照题目的设定应该能到达,这里只是做一个完整性的返回处理)。
-
-
图解(以
airports = [10, 12, 13, 12, 14]为例) :-
初始化阶段:
airports数组长度n = 5,创建queue、visited和airlineMap。- 遍历
airports数组构建airlineMap,结果为{10=[0], 12=[1, 3], 13=[2], 14=[4]},表示航空公司10管理机场0,航空公司12管理机场1和3等等。 - 将起点机场索引
0加入queue,标记visited[0] = true,steps = 0。
-
第一轮 BFS(
steps = 0) :-
当前队列
queue中有元素0(代表机场索引),队列大小size = 1。 -
取出
0,判断未到终点。 -
查看相邻机场:
- 索引
0 - 1 = -1不合法(越界),不做操作。 - 索引
0 + 1 = 1合法且未被访问,标记visited[1] = true,将1加入queue。
- 索引
-
查看同航空公司机场:航空公司
10只有机场0,无其他同公司机场可探索。 -
第一轮结束,
steps加1,变为1。
-
-
第二轮 BFS(
steps = 1) :-
当前队列
queue中有元素1,队列大小size = 1。 -
取出
1,判断未到终点。 -
查看相邻机场:
- 索引
1 - 1 = 0已访问,不做操作。 - 索引
1 + 1 = 2合法且未被访问,标记visited[2] = true,将2加入queue。
- 索引
-
查看同航空公司机场:航空公司
12还有机场3未访问,标记visited[3] = true,将3加入queue,然后移除航空公司12在airlineMap中的记录(防止重复访问)。 -
第二轮结束,
steps加1,变为2。
-
-
第三轮 BFS(
steps = 2) :-
当前队列
queue中有元素2和3,队列大小size = 2。 -
先取出
2,判断未到终点。-
查看相邻机场:
- 索引
2 - 1 = 1已访问,不做操作。 - 索引
2 + 1 = 3已在队列中,不做操作。
- 索引
-
查看同航空公司机场:航空公司
13只有机场2,无其他同公司机场可探索。
-
-
再取出
3,判断未到终点。-
查看相邻机场:
- 索引
3 - 1 = 2已访问,不做操作。 - 索引
3 + 1 = 4合法且未被访问,标记visited[4] = true,将4加入queue。
- 索引
-
查看同航空公司机场:航空公司
12已被移除记录,无操作。
-
-
第三轮结束,
steps加1,变为3。
-
-
第四轮 BFS(
steps = 3) :- 当前队列
queue中有元素4,队列大小size = 1。 - 取出
4,此时4为终点(n - 1 = 4),直接返回steps = 3,代表最少起飞次数为3。
- 当前队列
-
-
代码详解:
收起
java
复制
import java.util.*;
public class Main {
public static int solution(int[] airports) {
int n = airports.length;
if (n == 1) return 0; // 只有一个机场,起飞次数为0
// 使用队列进行广度优先搜索
Queue<Integer> queue = new LinkedList<>();
boolean[] visited = new boolean[n];
Map<Integer, List<Integer>> airlineMap = new HashMap<>();
// 记录相同航空公司机场的索引
for (int i = 0; i < n; i++) {
airlineMap.putIfAbsent(airports[i], new ArrayList<>());
airlineMap.get(airports[i]).add(i);
}
// 初始条件
queue.offer(0);
visited[0] = true;
int steps = 0;
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
int current = queue.poll();
// 如果到达终点
if (current == n - 1) {
return steps;
}
// 相邻机场
if (current - 1 >= 0 &&!visited[current - 1]) {
visited[current - 1] = true;
queue.offer(current - 1);
}
if (current + 1 < n &&!visited[current + 1]) {
visited[current + 1] = true;
queue.offer(current + 1);
}
// 相同航空公司机场
List<Integer> sameAirlineAirports = airlineMap.get(airports[current]);
if (sameAirlineAirports!= null) {
for (int next : sameAirlineAirports) {
if (!visited[next]) {
visited[next] = true;
queue.offer(next);
}
}
// 清空相同航空公司的记录,防止重复访问
airlineMap.remove(airports[current]);
}
}
steps++;
}
return -1; // 如果无法到达终点(理论上不应该发生)
}
public static void main(String[] args) {
// 测试用例
int[] airports1 = {10, 12, 13, 12, 14};
System.out.println(solution(airports1)); // 应该输出3
}
}
-
函数开头部分:
- 获取输入的
airports数组长度n,先判断如果n等于1,说明只有一个机场,无需起飞就能到达(其实就是已经在终点了),直接返回0。
- 获取输入的
-
数据结构初始化部分:
- 创建一个
LinkedList实现的队列queue,用于 BFS 存储待访问的机场索引,创建布尔数组visited并初始化为false,用于标记机场是否已访问,还创建了HashMap类型的airlineMap,其键为航空公司编号,值为该航空公司管理的机场索引的ArrayList。 - 通过
for循环遍历airports数组,对于每个机场元素airports[i],使用putIfAbsent方法确保在airlineMap中该航空公司对应的ArrayList已存在(如果不存在则创建一个新的空ArrayList),然后将当前机场索引i添加到对应的ArrayList中,这样就构建好了每个航空公司旗下机场索引的映射关系。
- 创建一个
-
BFS 准备阶段:
- 将起点机场索引
0通过queue.offer(0)加入队列,表示从起点开始搜索,标记visited[0] = true表示起点已访问,初始化起飞次数(步数)计数器steps为0。
- 将起点机场索引
-
BFS 主循环部分:
-
while (!queue.isEmpty())循环表示只要队列不为空,就持续进行 BFS 搜索。 -
在每一轮循环开始时,获取当前队列的大小
size,它代表当前层(同一轮可到达的机场集合)的节点数量。 -
内层
for循环通过i从0到size - 1遍历当前层的节点,每次取出队列头部的机场索引current = queue.poll()。 -
接着进行判断和操作:
-
if (current == n - 1)判断当前机场索引是否为终点索引(n - 1),如果是,则直接返回当前的起飞次数steps,表示找到了到达终点的最少起飞次数。 -
处理相邻机场部分:
if (current - 1 >= 0 &&!visited[current - 1])判断当前机场索引减1后的机场是否合法(不越界)且未被访问过,如果满足条件,就标记visited[current - 1] = true表示已访问,并通过queue.offer(current - 1)将其加入队列,以便后续继续探索该机场能到达的其他机场。- 同理,
if (current + 1 < n &&!visited[current + 1])对当前机场索引加1后的机场做相同的合法性、未访问判断以及相应的标记和入队操作。
-
处理同航空公司机场部分:
-
通过
airlineMap.get(airports[current])获取与当前机场current同属一家航空公司的其他机场索引列表sameAirlineAirports,如果该列表不为null(说明存在同航空公司的其他机场),则遍历这个列表:- 对于列表中的每个机场索引
next,通过if (!visited[next])判断是否未被访问过,如果未被访问,就标记visited[next] = true表示已访问,并通过queue.offer(next)将其加入队列,这样就能继续探索从这个同航空公司机场出发能到达的其他机场。 - 最后,通过
airlineMap.remove(airports[current])移除当前航空公司的记录,防止后续重复访问同属该航空公司的机场(因为同一家航空公司的机场之间是相互可达的,一次访问即可探索完所有相关机场)。
- 对于列表中的每个机场索引
-
-
-
在每一轮(外层
while循环的一次完整循环)结束后,通过steps++将起飞次数(步数)加1,表示进入下一层(下一轮可到达的机场集合)需要多一次起飞操作。
-
-
返回结果部分:
- 如果整个 BFS 循环结束后还未到达终点(也就是
while循环正常结束,队列变为空了),则返回-1,表示按照给定规则无法到达终点(理论上按照题目设定这种情况不应该出现,但这里做一个完整性的返回处理)。
- 如果整个 BFS 循环结束后还未到达终点(也就是
-
主函数
main部分:- 在
main方法中,创建了一个测试用例数组airports1,并调用solution方法传入该数组,将返回结果输出到控制台,用于简单验证solution方法在这个特定测试用例下是否能正确返回预期的最少起飞次数(这里预期输出为3)。
- 在
知识总结
-
新知识点梳理:
- 广度优先搜索(BFS)算法的应用场景和实现方式:本题通过 BFS 来解决在特定规则下求最少起飞次数(最短路径)的问题,了解到 BFS 适合用于求从起点到终点的最短步数、最小代价等场景,通过队列来存储待访问的节点,按照层次依次向外扩展搜索,保证先访问到的节点距离起点的步数是最短的。
HashMap与ArrayList配合构建复杂映射关系:学会了使用HashMap来存储不同键(本题中的航空公司编号)对应的值(同航空公司的机场索引列表,用ArrayList存储),这种组合可以方便地处理一对多的映射情况,在需要根据某个标识查找相关的多个元素时非常实用,比如本题中根据航空公司查找其管理的所有机场。- 标记数组(布尔数组)在避免重复访问中的作用:认识到通过创建布尔数组来标记节点(机场)是否已被访问,能够有效地避免在搜索过程中重复访问同一个节点,防止陷入死循环,提高搜索效率,这是很多搜索类算法中常用的技巧。
-
理解:
- BFS 算法就像是在一张地图上从起点开始一层一层地向外探索,每一层的节点都是距离起点相同步数能到达的地方,通过队列来有序地管理待探索的节点,保证了搜索的顺序性和最短性。而
HashMap和ArrayList的组合则像是一个分类清晰的索引库,能够快速定位到某个类别(航空公司)下的多个具体元素(机场)。标记数组则是给已经探索过的 “地方” 做个记号,避免重复走冤枉路,它们共同配合帮助我们高效地解决这类路径搜索和遍历相关的问题。
- BFS 算法就像是在一张地图上从起点开始一层一层地向外探索,每一层的节点都是距离起点相同步数能到达的地方,通过队列来有序地管理待探索的节点,保证了搜索的顺序性和最短性。而
-
学习建议(针对入门同学) :
- 对于 BFS 算法,先从简单的图结构(比如用二维数组表示的方格地图等)入手学习其基本原理和代码实现框架,可以手动模拟算法执行过程,画一下每一轮搜索时队列的变化、节点的访问情况等,加深对层次搜索的理解。然后尝试做一些基础的 BFS 练习题,比如在简单迷宫中求从起点到终点的最短步数等题目,逐渐掌握如何根据具体题目设定来构建队列、处理节点访问和判断终止条件等操作。
- 在学习
HashMap和ArrayList配合使用时,先分别熟悉HashMap和ArrayList的基本操作(如HashMap的put、get、putIfAbsent等方法,ArrayList的add、get、remove等方法),通过简单的代码示例去创建、操作这两种数据结构。然后再结合实际的一对多映射场景,像本题这样,练习如何将数据按照逻辑关系存入HashMap中,以及如何从HashMap中取出对应的ArrayList并进行遍历操作,多写一些类似的代码来巩固理解。 - 对于标记数组的使用,要明白其目的是避免重复访问,在做练习题时,每次遇到需要遍历搜索的题目,都思考一下是否可能出现重复访问的情况,尝试主动运用标记数组来解决,通过不断实践来养成使用这个技巧的习惯,提高代码的正确性和效率。