华为OD机试 2024D卷题库疯狂收录中,刷题点这里
专栏导读
本专栏收录于《华为OD机试(JAVA)真题(D卷+C卷+A卷+B卷)》。
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天CSDN在线答疑。
一、题目描述
给定一个有向图,图中可能包含有环,图使用二维矩阵表示,每一行的第一列表示起始节点,第二列表示终止节点,如 [0, 1] 表示从 0 到 1 的路径。
每个节点用正整数表示。
求这个数据的首节点与尾节点,题目给的用例会是一个首节点,但可能存在多个尾节点。同时图中可能含有环。如果图中含有环,返回 [-1]。
说明:入度为 0 是首节点,出度为 0 是尾节点。
二、输入描述
第一行为后续输入 A 的键值对 Q 数量 (N ≥ 0)
第二行为 2N 个数字。每两个为一组一个起点,一个终点,如:
4
1 2 2 3 3 1 3 4
三、输出描述
输出一个首节点和尾节点。如果有多个尾节点,按从大到小的顺序输出。
备注
- 如果图有环,输出为 [-1]
- 所有输入均合法,不会出现不匹配的数据
四、测试用例
测试用例1:
1、输入
4
0 1 0 2 1 2 2 3
2、输出
0 3
3、说明
输入表示一个有向图,包含了4条边(键值对数量为4)。具体如下:
4
0 1
0 2
1 2
2 3
将输入分解后,我们得到以下4条边:
从节点 0 到节点 1
从节点 0 到节点 2
从节点 1 到节点 2
从节点 2 到节点 3
处理逻辑
计算每个节点的入度和出度:
节点 0:
入度:0(没有任何边指向节点 0)
出度:2(节点 0 指向节点 1 和节点 2)
节点 1:
入度:1(节点 0 指向节点 1)
出度:1(节点 1 指向节点 2)
节点 2:
入度:2(节点 0 和节点 1 都指向节点 2)
出度:1(节点 2 指向节点 3)
节点 3:
入度:1(节点 2 指向节点 3)
出度:0(没有任何边从节点 3 指出)
确定首节点和尾节点:
首节点(入度为0的节点):
节点 0 是入度为0的节点,因此它是首节点。
尾节点(出度为0的节点):
节点 3 是出度为0的节点,因此它是尾节点。
输出结果:
根据题目的要求,我们按从大到小的顺序输出首节点和尾节点。
测试用例2:
1、输入
2
0 1 0 2
2、输出
0 1 2
3、说明
无
五、解题思路
1、拓扑排序
拓扑排序是一种用于有向无环图(DAG)的线性排序,使得对于每个边 (u, v),节点 u 在节点 v 之前出现。简单来说,就是将有向图的所有节点排序,保证每个节点都在它指向的节点之前。
最常用的两种拓扑排序算法是Kahn算法和基于DFS(深度优先搜索)的算法。
2、为什么采用拓扑排序?
题目要求找到有向图中的首节点和尾节点,同时需要判断图中是否存在环。如果存在环,则输出-1;如果没有环,则输出所有首节点和尾节点,这个问题可以通过拓扑排序有效地解决。
3、具体步骤:
- 构建邻接表:
- 从输入数据中读取节点和边,构建有向图的邻接表表示法。
- 使用HashMap存储每个节点及其对应的邻居节点列表。
- 使用HashSet存储所有出现过的节点,用于后续计算入度和出度。
- 检测环:
- 使用拓扑排序算法(Kahn算法)检测图中是否有环。
- 初始化每个节点的入度为0,遍历邻接表,计算每个节点的入度。
- 使用队列保存所有入度为0的节点,并逐个处理这些节点,将其邻居节点的入度减1。如果邻居节点的入度变为0,将其加入队列。
- 如果处理完所有入度为0的节点后,仍有未处理的节点,说明图中存在环。
- 找首节点和尾节点:
- 首节点:入度为0的节点。
- 尾节点:出度为0的节点。
- 通过遍历所有节点,计算每个节点的入度和出度,找出入度为0的节点作为首节点,出度为0的节点作为尾节点。
- 输出结果:
- 如果检测到图中有环,输出-1。
- 如果没有环,按升序输出所有首节点和尾节点。
六、Java算法源码
public class OdTest {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取键值对的数量
int n = scanner.nextInt();
int[][] edges = new int[n][2];
// 读取所有边的起始和结束节点
for (int i = 0; i < n; i++) {
edges[i][0] = scanner.nextInt();
edges[i][1] = scanner.nextInt();
}
// 构建邻接表
Map<Integer, List<Integer>> graph = new HashMap<>();
Set<Integer> nodes = new HashSet<>();
for (int[] edge : edges) {
graph.putIfAbsent(edge[0], new ArrayList<>());
graph.get(edge[0]).add(edge[1]);
nodes.add(edge[0]);
nodes.add(edge[1]);
}
// 检查图中是否有环
if (hasCycle(graph, nodes)) {
System.out.println(-1);
return;
}
// 找首节点和尾节点
List<Integer> heads = new ArrayList<>();
List<Integer> tails = new ArrayList<>();
// 初始化入度和出度
Map<Integer, Integer> inDegree = new HashMap<>();
Map<Integer, Integer> outDegree = new HashMap<>();
for (int node : nodes) {
inDegree.put(node, 0);
outDegree.put(node, 0);
}
// 计算每个节点的入度和出度
for (int node : graph.keySet()) {
for (int neighbor : graph.get(node)) {
inDegree.put(neighbor, inDegree.get(neighbor) + 1);
outDegree.put(node, outDegree.get(node) + 1);
}
}
// 找入度为0的节点(首节点)和出度为0的节点(尾节点)
for (int node : nodes) {
if (inDegree.get(node) == 0) {
heads.add(node);
}
if (outDegree.get(node) == 0) {
tails.add(node);
}
}
// 对首节点和尾节点进行排序
Collections.sort(heads);
Collections.sort(tails);
// 输出首节点和尾节点
// 输出首节点和尾节点
StringBuilder builder = new StringBuilder();
for(int i = 0;i<heads.size();i++){
builder.append(heads.get(i)).append(" ");
}
for(int i = 0;i<tails.size();i++){
builder.append(tails.get(i)).append(" ");
}
System.out.println(builder.substring(0, builder.length()-1));
}
/**
* 检查图中是否存在环
* @param graph 邻接表表示的图
* @param nodes 图中的所有节点
* @return 如果存在环,返回true,否则返回false
*/
private static boolean hasCycle(Map<Integer, List<Integer>> graph, Set<Integer> nodes) {
// 初始化所有节点的入度
Map<Integer, Integer> inDegree = new HashMap<>();
for (int node : nodes) {
inDegree.put(node, 0);
}
// 计算每个节点的入度
for (int node : graph.keySet()) {
for (int neighbor : graph.get(node)) {
inDegree.put(neighbor, inDegree.get(neighbor) + 1);
}
}
// 使用队列保存入度为0的节点
Queue<Integer> queue = new LinkedList<>();
for (int node : inDegree.keySet()) {
if (inDegree.get(node) == 0) {
queue.add(node);
}
}
int count = 0;
// 拓扑排序处理
while (!queue.isEmpty()) {
int current = queue.poll();
count++;
if (graph.containsKey(current)) {
for (int neighbor : graph.get(current)) {
inDegree.put(neighbor, inDegree.get(neighbor) - 1);
if (inDegree.get(neighbor) == 0) {
queue.add(neighbor);
}
}
}
}
// 如果拓扑排序处理的节点数不等于总节点数,则存在环
return count != nodes.size();
}
}
七、效果展示
1、输入
4
0 1 0 2 1 2 2 3
2、输出
0 3
3、说明
🏆下一篇:华为OD机试 - 简易内存池 - 逻辑分析(Java 2024 D卷 200分)
🏆本文收录于,华为OD机试(JAVA)真题(D卷+C卷+A卷+B卷)
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天CSDN在线答疑。