【华为OD】微服务的集成测试 - 实践

【华为OD】微服务的集成测试

题目描述

现在有 n 个容器服务,服务的启动可能有一定的依赖性(有些服务启动没有依赖),其次服务自身启动加载会消耗一些时间。

给你一个 n×n 的二维矩阵 useTime,其中 useTime[i][i] = 10 表示服务 i 自身启动加载需要消耗 10 s,useTime[i][j] = 1 表示服务 i 启动依赖服务 j 启动完成,useTime[i][k] = 0,表示服务 i 启动不依赖服务 k。存在 0 <= i, j, k < n。

服务之间启动没有循环依赖(不会出现环),若想对任意一个服务 i 进行集成测试(服务 i 自身也需要加载),求最少需要等待多少时间。

输入描述

第一行输入服务总量 n,之后的 n 行表示服务启动的依赖关系以及自身启动加载耗时最后输入 k 表示计算需要等待多少时间后可以对服务 k 进行集成测试其中 1 <= k <= n,1 <= n <= 100

输出描述

最少需要等待多少时间(s)后可以对服务 k 进行集成测试

示例

示例一

输入:

3
5 0 0
1 5 0
0 1 5
3

输出:

15

说明:
服务 3 启动依赖服务 2,服务 2 启动依赖服务 1,由于服务 1,2,3 自身加载需要消耗 5 s,所以 5+5+5=15,需等待 15 s 后可以对服务 3 进行集成测试

示例二

输入:

3
5 0 0
1 10 1
1 0 11
2

输出:

26

说明:
服务 2 启动依赖服务 1 和服务 3,服务 3 启动需要依赖服务 1,服务 1,2,3 自身加载需要消耗 5 s,10 s,11 s,所以 5+10+11=26,需等待 26 s 后可以对服务 2 进行集成测试

示例三

输入:

4
2 0 0
0 3 0 0
1 1 4 0
1 1 1 5
4

输出:

12

说明:
服务 3 启动依赖服务 1 和服务 2,服务 4 启动需要依赖服务 1,2,3,服务 1,2,3,4 自身加载需要消耗 2 s,3 s,4 s,5 s,所以 3+4+5=12 s(因为服务 1 和服务 2 可以同时启动),需等待 12 s 后可以对服务 4 进行集成测试

示例四

输入:

5
1 0 0 0 0
0 2 0 0 0
1 1 3 0 0
1 1 0 4 0
0 0 1 1 5
5

输出:

11

说明:
服务 3 启动依赖服务 1 和服务 2,服务 4 启动需要依赖服务 1,2,服务 5 启动需要依赖服务 3,4,服务 1,2,3,4,5 自身加载需要消耗 1 s,2 s,3 s,4 s,5 s,所以 2+4+5=11 s(因为服务 1 和服务 2 可以同时启动,服务 3 和服务 4 可以同时启动),需等待 11 s 后可以对服务 5 进行集成测试

解题思路

这是一个典型的有向无环图(DAG)的关键路径问题。我们需要找到从所有依赖服务到目标服务的最长路径,因为所有依赖服务都必须完成后,目标服务才能启动。

核心思想:

  1. 将服务依赖关系建模为有向图
  2. 使用拓扑排序或DFS计算每个服务的最早完成时间
  3. 目标服务的最早完成时间就是答案

我将提供两种解法:拓扑排序法(BFS)深度优先搜索法(DFS)

解法一:拓扑排序法(BFS)

使用拓扑排序的思想,从入度为0的节点开始,逐层计算每个服务的最早完成时间。

Java实现

import java.util.*;
public class Solution1
{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[][] useTime = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
useTime[i][j] = sc.nextInt();
}
}
int k = sc.nextInt() - 1;
// 转换为0基索引
int result = calculateMinTime(useTime, k, n);
System.out.println(result);
sc.close();
}
private static int calculateMinTime(int[][] useTime, int target, int n) {
// 构建依赖图和入度数组
List<
List<
Integer>
> graph = new ArrayList<
>();
int[] inDegree = new int[n];
for (int i = 0; i < n; i++) {
graph.add(new ArrayList<
>());
}
// 构建图:如果服务i依赖服务j,则j->i有边
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i != j && useTime[i][j] >
0) {
graph.get(j).add(i);
inDegree[i]++;
}
}
}
// 拓扑排序计算最早完成时间
int[] finishTime = new int[n];
Queue<
Integer> queue = new LinkedList<
>();
// 将入度为0的节点加入队列
for (int i = 0; i < n; i++) {
if (inDegree[i] == 0) {
queue.offer(i);
finishTime[i] = useTime[i][i];
// 自身启动时间
}
}
while (!queue.isEmpty()) {
int current = queue.poll();
for (int next : graph.get(current)) {
// 更新依赖服务的最早完成时间
finishTime[next] = Math.max(finishTime[next],
finishTime[current] + useTime[next][next]);
inDegree[next]--;
if (inDegree[next] == 0) {
queue.offer(next);
}
}
}
return finishTime[target];
}
}

Python实现

from collections import deque
def calculate_min_time(use_time, target, n):
# 构建依赖图和入度数组
graph = [[] for _ in range(n)]
in_degree = [0] * n
# 构建图:如果服务i依赖服务j,则j->i有边
for i in range(n):
for j in range(n):
if i != j and use_time[i][j] >
0:
graph[j].append(i)
in_degree[i] += 1
# 拓扑排序计算最早完成时间
finish_time = [0] * n
queue = deque()
# 将入度为0的节点加入队列
for i in range(n):
if in_degree[i] == 0:
queue.append(i)
finish_time[i] = use_time[i][i] # 自身启动时间
while queue:
current = queue.popleft()
for next_service in graph[current]:
# 更新依赖服务的最早完成时间
finish_time[next_service] = max(finish_time[next_service],
finish_time[current] + use_time[next_service][next_service])
in_degree[next_service] -= 1
if in_degree[next_service] == 0:
queue.append(next_service)
return finish_time[target]
def solve_topological():
n = int(input())
use_time = []
for _ in range(n):
row = list(map(int, input().split()))
use_time.append(row)
k = int(input()) - 1 # 转换为0基索引
result = calculate_min_time(use_time, k, n)
print(result)
solve_topological()

C++实现

#include <iostream>
  #include <vector>
    #include <queue>
      #include <algorithm>
        using namespace std;
        int calculateMinTime(vector<vector<
        int>>
        & useTime, int target, int n) {
        // 构建依赖图和入度数组
        vector<vector<
        int>>
        graph(n);
        vector<
        int>
        inDegree(n, 0);
        // 构建图:如果服务i依赖服务j,则j->i有边
        for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
        if (i != j && useTime[i][j] >
        0) {
        graph[j].push_back(i);
        inDegree[i]++;
        }
        }
        }
        // 拓扑排序计算最早完成时间
        vector<
        int>
        finishTime(n, 0);
        queue<
        int> q;
        // 将入度为0的节点加入队列
        for (int i = 0; i < n; i++) {
        if (inDegree[i] == 0) {
        q.push(i);
        finishTime[i] = useTime[i][i];
        // 自身启动时间
        }
        }
        while (!q.empty()) {
        int current = q.front();
        q.pop();
        for (int next : graph[current]) {
        // 更新依赖服务的最早完成时间
        finishTime[next] = max(finishTime[next],
        finishTime[current] + useTime[next][next]);
        inDegree[next]--;
        if (inDegree[next] == 0) {
        q.push(next);
        }
        }
        }
        return finishTime[target];
        }
        int main() {
        int n;
        cin >> n;
        vector<vector<
        int>>
        useTime(n, vector<
        int>
        (n));
        for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
        cin >> useTime[i][j];
        }
        }
        int k;
        cin >> k;
        k--;
        // 转换为0基索引
        int result = calculateMinTime(useTime, k, n);
        cout << result << endl;
        return 0;
        }

解法二:深度优先搜索法(DFS)

使用DFS递归计算每个服务的最早完成时间,通过记忆化搜索避免重复计算。

Java实现

import java.util.*;
public class Solution2
{
private static int[][] useTime;
private static int[] memo;
private static int n;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
useTime = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
useTime[i][j] = sc.nextInt();
}
}
int k = sc.nextInt() - 1;
// 转换为0基索引
memo = new int[n];
Arrays.fill(memo, -1);
int result = dfs(k);
System.out.println(result);
sc.close();
}
private static int dfs(int service) {
if (memo[service] != -1) {
return memo[service];
}
int maxDependencyTime = 0;
// 查找所有依赖服务
for (int j = 0; j < n; j++) {
if (j != service && useTime[service][j] >
0) {
maxDependencyTime = Math.max(maxDependencyTime, dfs(j));
}
}
// 当前服务的最早完成时间 = 最大依赖完成时间 + 自身启动时间
memo[service] = maxDependencyTime + useTime[service][service];
return memo[service];
}
}

Python实现

def dfs(service, use_time, memo, n):
if memo[service] != -1:
return memo[service]
max_dependency_time = 0
# 查找所有依赖服务
for j in range(n):
if j != service and use_time[service][j] >
0:
max_dependency_time = max(max_dependency_time, dfs(j, use_time, memo, n))
# 当前服务的最早完成时间 = 最大依赖完成时间 + 自身启动时间
memo[service] = max_dependency_time + use_time[service][service]
return memo[service]
def solve_dfs():
n = int(input())
use_time = []
for _ in range(n):
row = list(map(int, input().split()))
use_time.append(row)
k = int(input()) - 1 # 转换为0基索引
memo = [-1] * n
result = dfs(k, use_time, memo, n)
print(result)
solve_dfs()

C++实现

#include <iostream>
  #include <vector>
    #include <algorithm>
      using namespace std;
      vector<vector<
      int>> useTime;
      vector<
      int> memo;
      int n;
      int dfs(int service) {
      if (memo[service] != -1) {
      return memo[service];
      }
      int maxDependencyTime = 0;
      // 查找所有依赖服务
      for (int j = 0; j < n; j++) {
      if (j != service && useTime[service][j] >
      0) {
      maxDependencyTime = max(maxDependencyTime, dfs(j));
      }
      }
      // 当前服务的最早完成时间 = 最大依赖完成时间 + 自身启动时间
      memo[service] = maxDependencyTime + useTime[service][service];
      return memo[service];
      }
      int main() {
      cin >> n;
      useTime.resize(n, vector<
      int>
      (n));
      for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++) {
      cin >> useTime[i][j];
      }
      }
      int k;
      cin >> k;
      k--;
      // 转换为0基索引
      memo.resize(n, -1);
      int result = dfs(k);
      cout << result << endl;
      return 0;
      }

解法三:优化的拓扑排序法

对拓扑排序进行优化,使代码更加简洁高效。

Java实现

import java.util.*;
public class Solution3
{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[][] matrix = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
matrix[i][j] = sc.nextInt();
}
}
int target = sc.nextInt() - 1;
System.out.println(solve(matrix, target, n));
sc.close();
}
private static int solve(int[][] matrix, int target, int n) {
// 构建邻接表和入度
List<
Integer>[] graph = new List[n];
int[] inDegree = new int[n];
int[] finishTime = new int[n];
for (int i = 0; i < n; i++) {
graph[i] = new ArrayList<
>();
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i != j && matrix[i][j] >
0) {
graph[j].add(i);
inDegree[i]++;
}
}
}
Queue<
Integer> queue = new LinkedList<
>();
for (int i = 0; i < n; i++) {
if (inDegree[i] == 0) {
queue.offer(i);
finishTime[i] = matrix[i][i];
}
}
while (!queue.isEmpty()) {
int curr = queue.poll();
for (int next : graph[curr]) {
finishTime[next] = Math.max(finishTime[next],
finishTime[curr] + matrix[next][next]);
if (--inDegree[next] == 0) {
queue.offer(next);
}
}
}
return finishTime[target];
}
}

Python实现

from collections import deque
def solve(matrix, target, n):
# 构建邻接表和入度
graph = [[] for _ in range(n)]
in_degree = [0] * n
finish_time = [0] * n
for i in range(n):
for j in range(n):
if i != j and matrix[i][j] >
0:
graph[j].append(i)
in_degree[i] += 1
queue = deque()
for i in range(n):
if in_degree[i] == 0:
queue.append(i)
finish_time[i] = matrix[i][i]
while queue:
curr = queue.popleft()
for next_node in graph[curr]:
finish_time[next_node] = max(finish_time[next_node],
finish_time[curr] + matrix[next_node][next_node])
in_degree[next_node] -= 1
if in_degree[next_node] == 0:
queue.append(next_node)
return finish_time[target]
def main():
n = int(input())
matrix = []
for _ in range(n):
row = list(map(int, input().split()))
matrix.append(row)
target = int(input()) - 1
print(solve(matrix, target, n))
main()

C++实现

#include <iostream>
  #include <vector>
    #include <queue>
      #include <algorithm>
        using namespace std;
        int solve(vector<vector<
        int>>
        & matrix, int target, int n) {
        // 构建邻接表和入度
        vector<vector<
        int>>
        graph(n);
        vector<
        int>
        inDegree(n, 0);
        vector<
        int>
        finishTime(n, 0);
        for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
        if (i != j && matrix[i][j] >
        0) {
        graph[j].push_back(i);
        inDegree[i]++;
        }
        }
        }
        queue<
        int> q;
        for (int i = 0; i < n; i++) {
        if (inDegree[i] == 0) {
        q.push(i);
        finishTime[i] = matrix[i][i];
        }
        }
        while (!q.empty()) {
        int curr = q.front();
        q.pop();
        for (int next : graph[curr]) {
        finishTime[next] = max(finishTime[next],
        finishTime[curr] + matrix[next][next]);
        if (--inDegree[next] == 0) {
        q.push(next);
        }
        }
        }
        return finishTime[target];
        }
        int main() {
        int n;
        cin >> n;
        vector<vector<
        int>>
        matrix(n, vector<
        int>
        (n));
        for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
        cin >> matrix[i][j];
        }
        }
        int target;
        cin >> target;
        target--;
        // 转换为0基索引
        cout <<
        solve(matrix, target, n) << endl;
        return 0;
        }

算法复杂度分析

解法一:拓扑排序法(BFS)

  • 时间复杂度:O(N²),需要构建图和进行拓扑排序
  • 空间复杂度:O(N²),存储邻接表和辅助数组

解法二:深度优先搜索法(DFS)

  • 时间复杂度:O(N²),每个节点最多访问一次,每次访问需要检查所有依赖
  • 空间复杂度:O(N),递归栈深度和记忆化数组

解法三:优化的拓扑排序法

  • 时间复杂度:O(N²),与标准拓扑排序相同
  • 空间复杂度:O(N²),但代码更简洁

算法原理详解

核心思想

这个问题本质上是求有向无环图中的关键路径长度。每个服务可以看作图中的一个节点,依赖关系构成有向边,服务的启动时间是节点的权重。

关键概念

  1. 依赖关系建模:如果服务A依赖服务B,则从B到A有一条有向边
  2. 最早完成时间:服务的最早完成时间 = max(所有依赖服务的完成时间) + 自身启动时间
  3. 并行启动:没有依赖关系的服务可以并行启动

两种解法的区别

  1. 拓扑排序法

    • 从入度为0的节点开始,逐层计算
    • 适合理解服务启动的时间顺序
    • 代码结构清晰,易于调试
  2. DFS法

    • 从目标节点开始,递归计算依赖
    • 使用记忆化避免重复计算
    • 代码更简洁,但递归深度可能较大

示例分析

示例三详细分析

服务依赖关系:

服务1: 启动时间2s,无依赖
服务2: 启动时间3s,无依赖
服务3: 启动时间4s,依赖服务1和服务2
服务4: 启动时间5s,依赖服务1、服务2和服务3

启动时序:

  1. t=0: 服务1和服务2并行启动
  2. t=2: 服务1完成启动
  3. t=3: 服务2完成启动,服务3开始启动
  4. t=7: 服务3完成启动(3+4=7),服务4开始启动
  5. t=12: 服务4完成启动(7+5=12)

所以答案是12秒。

示例四详细分析

服务依赖关系:

服务1: 启动时间1s,无依赖
服务2: 启动时间2s,无依赖
服务3: 启动时间3s,依赖服务1和服务2
服务4: 启动时间4s,依赖服务1和服务2
服务5: 启动时间5s,依赖服务3和服务4

启动时序:

  1. t=0: 服务1和服务2并行启动
  2. t=1: 服务1完成启动
  3. t=2: 服务2完成启动,服务3和服务4并行启动
  4. t=5: 服务3完成启动(2+3=5)
  5. t=6: 服务4完成启动(2+4=6),服务5开始启动
  6. t=11: 服务5完成启动(6+5=11)

所以答案是11秒。

优化技巧

1. 输入处理优化

// 可以在读入时直接构建图,避免二次遍历
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
int val = sc.nextInt();
if (i == j) {
selfTime[i] = val;
} else if (val >
0) {
graph[j].add(i);
inDegree[i]++;
}
}
}

2. 边界条件处理

// 处理目标服务无依赖的情况
if (inDegree[target] == 0) {
return matrix[target][target];
}

3. 内存优化

对于大规模数据,可以使用邻接表代替邻接矩阵存储图结构,节省空间。

总结

两种解法都能正确解决问题,选择建议:

  1. 拓扑排序法

    • 逻辑清晰,易于理解和调试
    • 适合对算法过程有清晰认知的场景
    • 推荐用于面试和学习
  2. DFS法

    • 代码简洁,实现快速
    • 适合竞赛等对编码速度要求高的场景
    • 需要注意递归深度限制

核心要点:

  • 正确理解依赖关系的建模方式
  • 掌握拓扑排序和DFS两种图算法
  • 注意并行启动的处理逻辑
  • 仔细处理输入格式和索引转换

对于华为OD机试,建议掌握拓扑排序法,因为它更容易验证正确性,且在面试中能更好地展示算法思维。

posted @ 2025-09-15 12:51  yfceshi  阅读(16)  评论(0)    收藏  举报