大厂真题 / 华为

华为 6.3 笔试真题 - 研发岗(非AI方向)

本场考试概述

考试时间:2026年6月3日 考试岗位:研发岗(通软、嵌软、测试、普通算法、数据科学等非AI方向) 难度评级:中等偏难

考点分析

  1. 第一题:数字标识符压缩算法——模拟(难度中等)
  2. 第二题:爆破小游戏——树上DFS(难度中等)
  3. 第三题:昇腾NPU协同调度系统——回溯搜索 + 状态压缩(难度困难)

建议策略

  1. 前两题的字符串模拟和树形搜索是华为研发岗高频考点,务必稳拿
  2. 第一题关键是规则顺序——先合并连续相同数字组再去前导零,判断相同数字组时基于原始 4 位分组
  3. 第三题状态压缩 + 回溯偏难,时间紧张时先保证前两题正确,再攻坚第三题

第 1 题:数字标识符压缩算法

题目描述

标准数字标识符由 32 位组成,表示为 8 组 4 位小写十六进制数,用冒号分隔。按以下三条规则压缩:

  • 规则一(前导零压缩):每组去掉前导零,如 0db8db800000
  • 规则二(连续相同数字组压缩):若干个连续、彼此完全相同、且该组 4 位字符也完全相同的组(如 0000aaaa)可合并为 **,整个标识符中只能使用一次。取最长段,长度相同取最左
  • 规则三(优先级):先应用规则二,再应用规则一

样例

输入

2001:0db8:85a3:0010:0001:8a2e:0370:7334

输出

2001:db8:85a3:10:1:8a2e:370:7334

思路分析

第一步:观察题目性质

组数固定为 8,每组固定 4 个字符,直接模拟即可。关键是规则的执行顺序:必须在原始 4 位分组上完成连续相同段的查找,否则去掉前导零后 0000 变成 0,长度改变就无法正确判断”4 位字符完全相同”。

第二步:判定”相同数字组”

一个组只有当 4 位字符完全相同(如 aaaa0000)时才可能参与 ** 合并。

第三步:扫描最长连续段

从左到右遍历,遇到相同数字组时向右延伸统计连续段长度。维护最长段的起点和长度,只在严格更长时更新——并列时自动保留最左一段。

第四步:重建结果

走到最长段起点时输出 ** 并跳过整段,其余组去前导零后输出,用冒号拼接。

题解代码

import sys
input = sys.stdin.readline

def solve():
    s = input().strip()
    groups = s.split(":")

    best_start = -1
    best_len = 0
    i = 0
    while i < len(groups):
        g = groups[i]
        if len(g) == 4 and g[0] == g[1] == g[2] == g[3]:
            j = i + 1
            while j < len(groups) and groups[j] == groups[i]:
                j += 1
            if j - i > best_len:
                best_len = j - i
                best_start = i
            i = j
        else:
            i += 1

    result = []
    i = 0
    while i < len(groups):
        if i == best_start:
            result.append("**")
            i += best_len
        else:
            result.append(groups[i].lstrip('0') or '0')
            i += 1

    print(":".join(result))

solve()

复杂度分析

时间复杂度:$O(n \cdot k)$,$n = 8$ 组,$k = 4$ 字符,常数级 空间复杂度:$O(n)$,存储分组与结果


第 2 题:爆破小游戏

题目描述

游戏地图是一棵带边权的树,每个节点有收益值。炸弹放在某个节点上,影响范围为 $k$:到炸弹所在节点的距离(路径上边权之和)不超过 $k$ 的节点全部被爆破,获得收益之和。选择最优放置点使收益最大。

$n \leq 1000$。

样例

输入

7 3
10 10 10 300 10 10 300
0 1 1
0 2 1
1 3 3
1 4 1
2 5 3
2 6 1

输出

640

思路分析

第一步:观察题目性质

树上任意两点路径唯一,所以从放置点出发做 DFS,沿途累加距离,只要不超过 $k$ 就收集收益。$n \leq 1000$,枚举每个节点做一次 DFS 总复杂度 $O(n^2)$,完全可以接受。

第二步:距离受限的 DFS

从放置点出发递归,带上到当前节点的累计距离 $d$。若 $d > k$ 直接返回 0(剪枝——边权非负,子树只会更远)。否则累加当前节点收益并递归所有子节点。

第三步:取全局最大值

枚举所有 $n$ 个节点作为放置点,取最大收益。

以样例为例,炸弹放在节点 2:

  • 节点 2 自身距离 0 ≤ 3,收益 10
  • 节点 0 距离 1 ≤ 3,收益 10
  • 节点 1 距离 2 ≤ 3,收益 10
  • 节点 6 距离 1 ≤ 3,收益 300
  • 节点 4 距离 3 ≤ 3,收益 10
  • 节点 5 距离 3 ≤ 3,收益 10(实际样例放节点2只得部分)

放在节点 1 时:节点 1(10) + 节点 0(10, 距离1) + 节点 2(10, 距离2) + 节点 4(10, 距离1) + 节点 6(300, 距离3) + 节点 3(300, 距离3) = 640。

题解代码

import sys
from sys import setrecursionlimit
setrecursionlimit(100000)
input = sys.stdin.readline

def solve():
    n, k = map(int, input().split())
    g = list(map(int, input().split()))
    tree = [[] for _ in range(n)]
    for _ in range(n - 1):
        u, v, w = map(int, input().split())
        tree[u].append((v, w))
        tree[v].append((u, w))

    def dfs(u, parent, dist):
        if dist > k:
            return 0
        total = g[u]
        for v, w in tree[u]:
            if v != parent:
                total += dfs(v, u, dist + w)
        return total

    ans = 0
    for s in range(n):
        ans = max(ans, dfs(s, -1, 0))
    print(ans)

solve()

复杂度分析

时间复杂度:$O(n^2)$,枚举 $n$ 个放置点,每次 DFS 最多访问 $n$ 个节点 空间复杂度:$O(n)$,邻接表与递归栈


第 3 题:昇腾NPU协同调度系统

题目描述

管理昇腾 NPU 上的训练任务。每个任务有三个属性:需要占用的 NPU 卡编号列表、执行时间片数、上游依赖(至多 1 个,-1 表示无依赖)。

调度规则:

  • 每张卡同一时间只能执行一个任务
  • 任务必须在上游完成后才能开始
  • 任务一旦开始不能被抢占
  • 求所有任务全部完成的最短总耗时

任务数 $\leq 10$,NPU 卡数 $\leq 10$。

样例

输入

2 3
0
3
-1
0
2
-1
1
4
1

输出

6

思路分析

第一步:观察题目性质

任务之间会抢同一张卡、还有先后依赖,不存在一种固定排法总是最优。但任务数 $\leq 10$,所有执行顺序最多 $10!$ 种,可以暴力枚举所有合法顺序取最优。

第二步:贪心确定开始时间

对于一个固定的调度顺序,每个任务都”能多早就多早”开始一定是最优的。原因是:任务一旦开始不能中断、时长固定,让某任务提前开始只会让它占的卡更早空出来,绝不会让任何后续任务变晚。

所以对固定顺序,每个任务的开始时间 = $\max(\text{上游完成时间}, \text{所需各卡的最早可用时间})$。

第三步:回溯枚举 + 剪枝

用位掩码表示已安排的任务集合,逐个挑能开工的任务往后放。每多排一个任务,总耗时只增不减——一旦当前耗时已追平已知最优解,直接剪掉该分支。

第四步:位掩码存储所需卡

把任务所需的卡编号列表压成一个整数(位掩码),用位运算快速判断某张卡是否被占用。

题解代码

import sys
input = sys.stdin.readline

def solve():
    card_cnt, task_cnt = map(int, input().split())
    need = []
    dur = []
    pre = []
    for i in range(task_cnt):
        cards = list(map(int, input().split()))
        mask = 0
        for c in cards:
            mask |= (1 << c)
        need.append(mask)
        dur.append(int(input()))
        pre.append(int(input()))

    free_at = [0] * card_cnt
    finish = [0] * task_cnt
    best = [float('inf')]

    def dfs(done, cur_time):
        if done == (1 << task_cnt) - 1:
            best[0] = min(best[0], cur_time)
            return
        if cur_time >= best[0]:
            return
        for i in range(task_cnt):
            if done & (1 << i):
                continue
            if pre[i] != -1 and not (done & (1 << pre[i])):
                continue
            start = finish[pre[i]] if pre[i] != -1 else 0
            for c in range(card_cnt):
                if need[i] & (1 << c):
                    start = max(start, free_at[c])
            end = start + dur[i]
            new_time = max(cur_time, end)
            if new_time >= best[0]:
                continue
            old_free = free_at[:]
            old_finish = finish[i]
            for c in range(card_cnt):
                if need[i] & (1 << c):
                    free_at[c] = end
            finish[i] = end
            dfs(done | (1 << i), new_time)
            for c in range(card_cnt):
                free_at[c] = old_free[c]
            finish[i] = old_finish

    dfs(0, 0)
    print(best[0])

solve()

复杂度分析

时间复杂度:最坏 $O(m! \cdot c)$,$m$ 为任务数,$c$ 为卡数。$m \leq 10$,加上剪枝实际远小于上界 空间复杂度:$O(m + c)$,存任务完成时刻、卡可用时刻和递归栈


小结

  • 第一题是模拟题,关键是规则优先级:先在原始 4 位分组上找最长连续相同段合并为 **,再去前导零
  • 第二题是树上 DFS,$n \leq 1000$ 允许枚举每个放置点各做一次距离受限的 DFS,注意边权非负的剪枝
  • 第三题是回溯 + 状态压缩,任务数 $\leq 10$ 决定了可以暴力枚举所有调度顺序,核心优化是”当前耗时已不可能更优则剪枝”