华为非AI方向笔试真题-昇腾NPU协同调度系统(详细思路+多语言题解)
华为非AI方向0527笔试第三题【昇腾NPU协同调度系统】,提供详细思路讲解以及代码实现。
昇腾NPU协同调度系统
华为笔试真题 6月3号 非AI方向 300分题型
题目内容
设计一个调度系统管理昇腾 NPUNPUNPU 上的训练任务,需满足以下条件:
任务属性(每个任务包含333个维度)
- npusnpusnpus:所需 NPU 卡编号列表
- durationdurationduration:执行需要的时间片数
- dependondependondependon:必须在该任务前完成的任务 IDIDID(只会依赖 000 或者 111 个上游任务)
调度规则 - 每张 NPUNPUNPU 卡同一时间只能执行一个任务
- 任务必须在前置任务完成后才能开始
- 时间片为正整数,多个满足条件的任务可并行启动,不可抢占已分配任务
- 可能存在多种调度方案,需要选出全部任务结束耗时最短的一种
用例保证存在可调度方案(不存在任务间循环依赖),输出所有任务全部完成需要的最短时间。
输入描述
第 111 行,NPUNPUNPU 卡总数(1<=NPU卡总数<=101<=NPU卡总数<=101<=NPU卡总数<=10),总任务数量(1<=总任务数量<=81<=总任务数量<=81<=总任务数量<=8),如:4 2
第 222 行,【第 2−42-42−4 行第 000 个任务】,任务 000 依赖的 npunpunpu 列表(空格分割),如:000 111
第 333 行,【第 2−42-42−4 行第 000 个任务】,任务 000 持续时间,如:555
第 444 行,【第 2−42-42−4 行第 000 个任务】,任务 000 依赖的上游任务 IDIDID(无依赖时固定为 −1-1−1),如:111
第 555 行,【第 5−75-75−7 行第 111 个任务】,任务 111 依赖的 npunpunpu 列表(空格分割),如:111 222
第 666 行,【第 5−75-75−7 行第 111 个任务】,任务 111 持续时间,如:444
第 777 行,【第 5−75-75−7 行第 111 个任务】,任务 111 依赖的上游任务 IDIDID(无依赖时固定为 −1-1−1),如:000
……,如果存在更多任务,333 行一组进行输入,任务 iii 依赖的 npunpunpu 列表(空格分割),如:111 222
……,如果存在更多任务,333 行一组进行输入,任务 iii 持续时间,如:444
……,如果存在更多任务,333 行一组进行输入,任务 iii 依赖的上游任务 IDIDID(无依赖时固定为 −1-1−1),如:000
注意:题目保证输入合法,无需校验输入
输出描述
格式:intintint 类型数值
解释:所有任务全部完成需要的最短时间,用例保证存在可调度方案(不存在任务间循环依赖)
样例1
输入
2 3
0
3
-1
0
2
-1
1
4
1
输出
6
说明
- 222 # 222个NPU卡,333个任务
- 000 # 任务000需要NPU卡000
- 333 # 任务000持续333
- −1-1−1 # 任务000无依赖
- 000 # 任务111需要NPU卡000
- 222 # 任务111持续222
- −1-1−1 # 任务111无依赖
- 111 # 任务222需要NPU卡111
- 444 # 任务222持续444
- 111 # 任务222依赖任务111
方案1:优先执行耗时长的任务(任务000先执行) - 时间000~333 NPU000: 任务000(持续333,时间000-333) NPU111: 空闲(任务222依赖任务111,还未完成)
- 时间333~555 NPU000: 任务111(持续222,时间333-555) NPU111: 空闲(任务222需等待任务111,任务111在时间555完成,但任务222还未开始)
- 时间555~999 NPU000: 空闲 NPU111: 任务222(持续444,时间555-999)
任务完成时间: 任务000: 时间333完成 任务111: 时间555完成 任务222: 时间999完成
总时间:999
方案2:优先执行耗时短的任务(任务111先执行) - 时间000~222 NPU000: 任务111(持续222,时间000-222) NPU111: 空闲(任务222依赖任务111,还未完成)
- 时间222~666 NPU000: 任务000(持续333,时间222-555) NPU111: 任务222(持续444,时间222-666,任务111在时间222已完成,满足依赖)
- 时间555~666 NPU000: 空闲(任务000已完成) NPU111: 任务222继续执行(时间555-666)
任务完成时间:
任务111: 时间222完成 任务000: 时间555完成 任务222: 时间666完成
总时间:666
样例2
输入
3 2
0 1
4
-1
1 2
5
0
输出
9
说明
- 333 222 # 333个NPU卡,222个任务
- 0 10\ 10 1 # 任务000需要NPU卡000、卡111
- 444 # 任务000持续444
- −1-1−1 # 任务000无依赖
- 1 21\ 21 2 # 任务111需要NPU卡111、卡222
- 555 # 任务111持续555
- 000 # 任务111依赖任务000
调度顺序: 任务111依赖任务000,只能先执行任务000再执行任务111,总耗时4+5=94+5=94+5=9,输出999
提示 NPUNPUNPU 编号从 000 开始,任务编号从 OOO 开始
题解
思路
递归回溯实现,由于本题任务数较小1 <= M <= 8完成可以枚举所有执行情况来确定最小时间。具体逻辑如下:
- 接受输入,记录每个任务的
依赖NPU,任务执行时长和依赖任务 - 定义
finish记录每个任务完成时间,npuAvalid记录每个NPU空闲时间,complete二进制记录每个任务完成标志。然后进行递归回溯 - 递归从前往后选取一个任务执行,选取任务需要满足以下条件
- 任务还未完成
- 任务依赖的任务已经完成
- 计算任务的开始时间
startTime,为依赖的npu空闲时间和依赖任务的最大值,任务结束时间就为startTime + duration, 更新对应finish以及依赖Npu的空闲时间,往下进行递归 - 递归结束条件为
complete == (1 << m) -1
C++
#include<bits/stdc++.h>
using namespace std;
struct Task {
vector<int> npus;
int duration;
int pre;
};
int ans = INT_MAX;
// NPU卡 // 任务数量
int m, n;
vector<Task> task;
vector<int> finish;
void dfs(int complete, vector<int>& npuAvail) {
// 所有任务都已调度,更新最优解
if (complete == (1 << n) - 1) {
int finishTime = *max_element(npuAvail.begin(), npuAvail.end());
ans = min(ans, finishTime);
return;
}
// 剪枝:当前时间已超过已知最优解
int curMax = *max_element(npuAvail.begin(), npuAvail.end());
if (curMax >= ans) return;
// 尝试调度一个任务
for (int i = 0; i < n; i++) {
// 跳过已完成的任务
if (complete & (1 << i)) continue;
// 检查依赖是否满足
if (task[i].pre != -1 && !(complete & (1 << task[i].pre))) {
continue;
}
// 计算任务最早开始时间
int startTime = 0;
for (int npu : task[i].npus) {
startTime = max(startTime, npuAvail[npu]);
}
// 晚于依赖任务完成时间
if (task[i].pre != -1) {
startTime = max(startTime, finish[task[i].pre]);
}
int endTime = startTime + task[i].duration;
// 记录NPU的原始状态,用于回溯
vector<int> oldAvail(m);
for (int npu : task[i].npus) {
oldAvail[npu] = npuAvail[npu];
npuAvail[npu] = endTime; // 分配NPU
}
finish[i] = endTime;
dfs(complete | (1 << i), npuAvail);
finish[i] = -1;
// 回溯:恢复NPU状态
for (int npu : task[i].npus) {
npuAvail[npu] = oldAvail[npu];
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> m >> n;
task.resize(n);
for (int i = 0; i < n; i++) {
string line;
cin.ignore();
getline(cin, line);
stringstream ss(line);
int x;
while (ss >> x) {
task[i].npus.push_back(x);
}
cin >> task[i].duration;
cin >> task[i].pre;
}
finish.resize(n, -1);
vector<int> npuAvail(m, 0);
dfs(0, npuAvail);
cout << ans << "\n";
}
java
import java.io.*;
import java.util.*;
public class Main {
static class Task {
List<Integer> npus = new ArrayList<>();
int duration;
int pre;
}
static int ans = Integer.MAX_VALUE;
// NPU卡数量 // 任务数量
static int m, n;
static List<Task> task = new ArrayList<>();
static int[] finish;
static void dfs(int complete, int[] npuAvail) {
// 所有任务都已调度,更新最优解
if (complete == (1 << n) - 1) {
int finishTime = 0;
for (int x : npuAvail) {
finishTime = Math.max(finishTime, x);
}
ans = Math.min(ans, finishTime);
return;
}
// 剪枝:当前时间已超过已知最优解
int curMax = 0;
for (int x : npuAvail) {
curMax = Math.max(curMax, x);
}
if (curMax >= ans) return;
// 尝试调度一个任务
for (int i = 0; i < n; i++) {
// 跳过已完成的任务
if ((complete & (1 << i)) != 0) continue;
// 检查依赖是否满足
if (task.get(i).pre != -1 &&
(complete & (1 << task.get(i).pre)) == 0) {
continue;
}
// 计算任务最早开始时间
int startTime = 0;
for (int npu : task.get(i).npus) {
startTime = Math.max(startTime, npuAvail[npu]);
}
// 晚于依赖任务完成时间
if (task.get(i).pre != -1) {
startTime = Math.max(startTime, finish[task.get(i).pre]);
}
int endTime = startTime + task.get(i).duration;
// 记录NPU原状态
int[] oldAvail = new int[m];
for (int npu : task.get(i).npus) {
oldAvail[npu] = npuAvail[npu];
npuAvail[npu] = endTime;
}
finish[i] = endTime;
dfs(complete | (1 << i), npuAvail);
finish[i] = -1;
// 回溯
for (int npu : task.get(i).npus) {
npuAvail[npu] = oldAvail[npu];
}
}
}
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] first = br.readLine().trim().split("\\s+");
m = Integer.parseInt(first[0]);
n = Integer.parseInt(first[1]);
for (int i = 0; i < n; i++) {
Task t = new Task();
String[] npuLine = br.readLine().trim().split("\\s+");
for (String s : npuLine) {
t.npus.add(Integer.parseInt(s));
}
t.duration = Integer.parseInt(br.readLine().trim());
t.pre = Integer.parseInt(br.readLine().trim());
task.add(t);
}
finish = new int[n];
Arrays.fill(finish, -1);
int[] npuAvail = new int[m];
dfs(0, npuAvail);
System.out.println(ans);
}
}
python
import sys
ans = float('inf')
# NPU卡数量 // 任务数量
m, n = map(int, input().split())
task = []
for _ in range(n):
npus = list(map(int, input().split()))
duration = int(input())
pre = int(input())
task.append({
"npus": npus,
"duration": duration,
"pre": pre
})
finish = [-1] * n
def dfs(complete, npu_avail):
global ans
# 所有任务都已调度,更新最优解
if complete == (1 << n) - 1:
finish_time = max(npu_avail)
ans = min(ans, finish_time)
return
# 剪枝:当前时间已超过已知最优解
cur_max = max(npu_avail)
if cur_max >= ans:
return
# 尝试调度一个任务
for i in range(n):
# 跳过已完成的任务
if complete & (1 << i):
continue
# 检查依赖是否满足
if task[i]["pre"] != -1 and not (complete & (1 << task[i]["pre"])):
continue
# 计算任务最早开始时间
start_time = 0
for npu in task[i]["npus"]:
start_time = max(start_time, npu_avail[npu])
# 晚于依赖任务完成时间
if task[i]["pre"] != -1:
start_time = max(start_time, finish[task[i]["pre"]])
end_time = start_time + task[i]["duration"]
old_avail = {}
for npu in task[i]["npus"]:
old_avail[npu] = npu_avail[npu]
npu_avail[npu] = end_time
finish[i] = end_time
dfs(complete | (1 << i), npu_avail)
finish[i] = -1
# 回溯
for npu in task[i]["npus"]:
npu_avail[npu] = old_avail[npu]
npu_avail = [0] * m
dfs(0, npu_avail)
print(ans)
javascript
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const lines = [];
rl.on('line', line => {
lines.push(line.trim());
});
rl.on('close', () => {
let idx = 0;
const [m, n] = lines[idx++].split(' ').map(Number);
const task = [];
for (let i = 0; i < n; i++) {
const npus = lines[idx++].split(' ').map(Number);
const duration = Number(lines[idx++]);
const pre = Number(lines[idx++]);
task.push({
npus,
duration,
pre
});
}
let ans = Number.MAX_SAFE_INTEGER;
const finish = new Array(n).fill(-1);
function dfs(complete, npuAvail) {
// 所有任务都已调度,更新最优解
if (complete === (1 << n) - 1) {
const finishTime = Math.max(...npuAvail);
ans = Math.min(ans, finishTime);
return;
}
// 剪枝:当前时间已超过已知最优解
const curMax = Math.max(...npuAvail);
if (curMax >= ans) {
return;
}
// 尝试调度一个任务
for (let i = 0; i < n; i++) {
// 跳过已完成的任务
if (complete & (1 << i)) continue;
// 检查依赖是否满足
if (
task[i].pre !== -1 &&
!(complete & (1 << task[i].pre))
) {
continue;
}
// 计算任务最早开始时间
let startTime = 0;
for (const npu of task[i].npus) {
startTime = Math.max(startTime, npuAvail[npu]);
}
// 晚于依赖任务完成时间
if (task[i].pre !== -1) {
startTime = Math.max(
startTime,
finish[task[i].pre]
);
}
const endTime = startTime + task[i].duration;
const oldAvail = {};
for (const npu of task[i].npus) {
oldAvail[npu] = npuAvail[npu];
npuAvail[npu] = endTime;
}
finish[i] = endTime;
dfs(complete | (1 << i), npuAvail);
finish[i] = -1;
// 回溯
for (const npu of task[i].npus) {
npuAvail[npu] = oldAvail[npu];
}
}
}
const npuAvail = new Array(m).fill(0);
dfs(0, npuAvail);
console.log(ans);
});
Go
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
type Task struct {
npus []int
duration int
pre int
}
var ans = int(^uint(0) >> 1)
// NPU卡数量 // 任务数量
var m, n int
var task []Task
var finish []int
func max(a, b int) int {
if a > b {
return a
}
return b
}
func dfs(complete int, npuAvail []int) {
// 所有任务都已调度,更新最优解
if complete == (1<<n)-1 {
finishTime := 0
for _, v := range npuAvail {
if v > finishTime {
finishTime = v
}
}
if finishTime < ans {
ans = finishTime
}
return
}
// 剪枝:当前时间已超过已知最优解
curMax := 0
for _, v := range npuAvail {
if v > curMax {
curMax = v
}
}
if curMax >= ans {
return
}
// 尝试调度一个任务
for i := 0; i < n; i++ {
// 跳过已完成的任务
if complete&(1<<i) != 0 {
continue
}
// 检查依赖是否满足
if task[i].pre != -1 &&
(complete&(1<<task[i].pre)) == 0 {
continue
}
// 计算任务最早开始时间
startTime := 0
for _, npu := range task[i].npus {
startTime = max(startTime, npuAvail[npu])
}
// 晚于依赖任务完成时间
if task[i].pre != -1 {
startTime = max(startTime, finish[task[i].pre])
}
endTime := startTime + task[i].duration
oldAvail := make(map[int]int)
for _, npu := range task[i].npus {
oldAvail[npu] = npuAvail[npu]
npuAvail[npu] = endTime
}
finish[i] = endTime
dfs(complete|(1<<i), npuAvail)
finish[i] = -1
// 回溯
for _, npu := range task[i].npus {
npuAvail[npu] = oldAvail[npu]
}
}
}
func main() {
reader := bufio.NewReader(os.Stdin)
line, _ := reader.ReadString('\n')
line = strings.TrimSpace(line)
first := strings.Fields(line)
m, _ = strconv.Atoi(first[0])
n, _ = strconv.Atoi(first[1])
task = make([]Task, n)
for i := 0; i < n; i++ {
line, _ = reader.ReadString('\n')
line = strings.TrimSpace(line)
fields := strings.Fields(line)
var npus []int
for _, s := range fields {
x, _ := strconv.Atoi(s)
npus = append(npus, x)
}
line, _ = reader.ReadString('\n')
duration, _ := strconv.Atoi(strings.TrimSpace(line))
line, _ = reader.ReadString('\n')
pre, _ := strconv.Atoi(strings.TrimSpace(line))
task[i] = Task{
npus: npus,
duration: duration,
pre: pre,
}
}
finish = make([]int, n)
for i := range finish {
finish[i] = -1
}
npuAvail := make([]int, m)
dfs(0, npuAvail)
fmt.Println(ans)
}
更多推荐




所有评论(0)