大厂真题 / 华为

华为 9.4 笔试真题 - AI 岗

本场考试概述

考试时间:2026 年 9 月 4 日

考试岗位:AI 岗

证据边界:材料称本场有 20 道选择题(15 道单选、5 道多选)和 2 道编程题,但正文只能够恢复 10 道单选题。本文仅收录这 10 道,不补写缺失的 10 道;两道编程题均完整整理。


第 1 部分:可恢复单选题

1. 多任务模型共享主干

“共享主干 + 任务头”结构最直接的工程收益是什么?

  • A. 保证所有任务精度同时提升
  • B. 完全消除任务间冲突
  • C. 减少重复参数和重复计算
  • D. 每个任务拥有完全独立的特征空间

答案:C。共享特征提取网络可复用参数和前向计算,但不能保证消除负迁移。

2. 单位向量的欧氏距离与余弦相似度

单位向量 $u,v$ 的欧氏距离平方与余弦相似度关系是什么?

  • A. $\lVert u-v\rVert_2^2=\cos^2\theta$
  • B. $\lVert u-v\rVert_2^2=1-\cos\theta$
  • C. $\lVert u-v\rVert_2^2=2-2\cos\theta$
  • D. 不存在固定关系

答案:C。展开平方并使用 $\lVert u\rVert=\lVert v\rVert=1$ 即得结论。

3. Top-p 采样

Top-p 参数为 0.85,商品概率依次为 $A(0.4),B(0.3),C(0.2),D(0.1)$,商品 A 被采样的概率是多少(保留 3 位小数)?

  • A. 0.400
  • B. 0.571
  • C. 0.471
  • D. 0.444

答案:D。最小核集合为 ${A,B,C}$,归一化后 $P(A)=0.4/0.9\approx0.444$。

4. 一元线性回归

模型 $\hat y=2x+1$,当 $x=4$ 时预测值是多少?

  • A. 10
  • B. 9
  • C. 8
  • D. 7

答案:B。

5. GRPO 与 PPO

GRPO 相比 PPO 的典型优势是什么?

  • A. 不需要采样
  • B. 必须训练 critic
  • C. 使用组内相对奖励代替 critic
  • D. 完全离线训练

答案:C。它使用同一提示下多个输出的组内相对奖励估计优势,从而省去价值网络。

6. RAG 文本切分

文档有 4800 个中文字符,约 1 个中文字符对应 0.7 token。chunk 大小 1024 token、重叠 128 token,至少需要多少个 chunk?

  • A. 6
  • B. 3
  • C. 4
  • D. 5

答案:C。总长约 3360 token,步长为 896;块数为 $1+\lceil(3360-1024)/896\rceil=4$。

7. 集中趋势

下列哪个不是描述随机变量集中趋势的统计量?

  • A. 数学期望
  • B. 中位数
  • C. 方差
  • D. 众数

答案:C。方差描述离散程度。

8. PCA 与 LDA

下列说法最准确的是?

  • A. PCA 最大化类间距离,LDA 最小化重构误差
  • B. PCA 有监督,LDA 无监督
  • C. PCA 最大化投影总方差;LDA 使类内聚集、类间分离
  • D. 有标签时 LDA 必定优于 PCA

答案:C。PCA 不使用标签;LDA 使用标签并优化类间散度与类内散度之比。

9. 伯努利分布的 MLE

抛硬币 10 次出现 7 次正面,正面概率 $p$ 的极大似然估计是多少?

  • A. 0.3
  • B. 0.7
  • C. 无法确定
  • D. 0.5

答案:B。伯努利参数的 MLE 是样本中成功比例 $7/10$。

10. Lagrange 与 Newton 插值

给定相同的 $n+1$ 个互异数据点,下列描述正确的是?

  • A. Newton 插值新增节点时要重算全部基函数
  • B. Lagrange 插值的代数精度更高
  • C. Lagrange 更适合动态增加节点
  • D. 两种表示得到的插值多项式恒等

答案:D。次数不超过 $n$ 且通过全部节点的插值多项式唯一;Newton 形式更便于增量加入节点。


第 2 部分:编程题

第 1 题:时间序列数据清洗与特征评分

题目描述

给定 $n$ 条按时间戳严格递增的记录 $(t_i,v_i,q_i)$。质量标记中,0 表示有效、1 表示可疑、2 表示无效。依次执行:

  1. 插值:仅处理 $q_i=2$ 的记录,前继/后继锚点只认 $q=0$。两侧都有锚点 $(t_a,v_a),(t_b,v_b)$ 时令 \(v_i=v_a+(v_b-v_a)\frac{t_i-t_a}{t_b-t_a};\) 只有一侧时复制该侧值;两侧都没有则保留原值。
  2. 异常平滑:窗口大小 $w$ 为奇数。位置 $i$ 的窗口是序列范围与 $[i-(w-1)/2,i+(w-1)/2]$ 的交集。窗口均值、总体标准差都由步骤 1 的同一份快照计算;若 $\lvert v_i-\mu\rvert>k\sigma$,则把该点替换为 $\mu$。所有替换统一写入新序列。
  3. 标准化:对步骤 2 的序列计算总体均值 $\bar v$ 和总体标准差 $s$,令 $z_i=(v_i-\bar v)/s$;若 $s=0$,所有 $z_i=0$。
  4. 评分:质量权重 $p_i$ 在 $q_i=0,1,2$ 时分别为 $1,0.5,0.25$。令 \(w_i=\frac{p_i\lvert v_i\rvert}{\sum_jp_j\lvert v_j\rvert},\qquad S=\sum_iw_i\lvert z_i\rvert.\) 若分母为 0,则 $S=0$。输出 $S\times100$ 四舍五入后的整数,恰为 0.5 时向上取整。

输入描述

第一行输入 $n,w,k$,其中

\[1\le n\le10^4,\quad 1\le w\le n,\quad w\text{ 为奇数},\quad 1\le k\le10.\]

接下来 $n$ 行输入 $t_i,v_i,q_i$,满足

\[1\le t_i\le10^9,\quad -10^9\le v_i\le10^9,\quad q_i\in\{0,1,2\},\]

且 $t_i$ 严格递增,至少有一条 $q_i=0$ 的记录。

输出描述

输出最终评分乘 100 后按题意取整的整数。

样例

输入

6 3 2
1 10 0
2 15 2
3 20 0
4 18 0
5 100 0
6 12 0

输出

152

算法

插值用正反两次扫描得到最近有效锚点。异常检测若使用“平方和减均值平方”,当数据整体很大但波动很小时会发生灾难性消减。为兼顾 $n=10^4$ 与数值稳定性,使用两层分块:

  • 每块维护值的总和、块均值与块内离差平方和 $M_2$;
  • 查询窗口时,把完整块通过 Chan 合并公式加入统计量,窗口两端不足整块的元素逐个加入;
  • 这样不再相减两个约 $10^{18}$ 的近似数,方差由中心化离差直接得到。

块长取约 $\sqrt n$,每个窗口访问 $O(\sqrt n)$ 个完整块与边缘元素,总复杂度 $O(n\sqrt n)$,在 $n=10^4$ 时可行。全局标准化同样用 Welford 在线算法计算稳定方差。

正确性证明

最近锚点扫描显然给出每个位置左右最近的 $q=0$ 记录,因此步骤 1 严格按题意插值。

对步骤 2,Welford 单点更新维护样本数、均值和离差平方和。Chan 公式精确描述两个不交集合统计量合并后的均值与离差平方和;每个窗口被拆成互不重叠的边缘单点和完整块,合并后覆盖且仅覆盖该窗口,因此得到窗口总体均值与方差。所有查询只读取插值后的 cleaned,结果另存,满足快照要求。

步骤 3 的 Welford 统计覆盖平滑后全部元素,故得到题定总体均值和标准差。步骤 4 逐项代入权重公式并按 floor(x+0.5) 取整。四步均正确,最终输出正确。

Python ACM 题解

import math
import sys


def merge_stats(a, b):
    na, ma, m2a = a
    nb, mb, m2b = b
    if na == 0:
        return b
    if nb == 0:
        return a
    delta = mb - ma
    n = na + nb
    mean = ma + delta * nb / n
    m2 = m2a + m2b + delta * delta * na * nb / n
    return n, mean, m2


def add_value(stat, x):
    n, mean, m2 = stat
    n2 = n + 1
    delta = x - mean
    mean2 = mean + delta / n2
    return n2, mean2, m2 + delta * (x - mean2)


def clean_values(ts, raw, quality):
    n = len(raw)
    previous = [-1] * n
    following = [-1] * n
    last = -1
    for i in range(n):
        previous[i] = last
        if quality[i] == 0:
            last = i
    last = -1
    for i in range(n - 1, -1, -1):
        following[i] = last
        if quality[i] == 0:
            last = i

    values = [float(x) for x in raw]
    for i in range(n):
        if quality[i] != 2:
            continue
        left, right = previous[i], following[i]
        if left != -1 and right != -1:
            ratio = (ts[i] - ts[left]) / (ts[right] - ts[left])
            values[i] = raw[left] + (raw[right] - raw[left]) * ratio
        elif left != -1:
            values[i] = float(raw[left])
        elif right != -1:
            values[i] = float(raw[right])
    return values


def smooth_values(values, window, threshold):
    n = len(values)
    block_size = max(1, math.isqrt(n))
    block_stats = []
    for start in range(0, n, block_size):
        stat = (0, 0.0, 0.0)
        for x in values[start:start + block_size]:
            stat = add_value(stat, x)
        block_stats.append(stat)

    def range_stats(left, right):
        stat = (0, 0.0, 0.0)
        while left <= right and left % block_size:
            stat = add_value(stat, values[left])
            left += 1
        while left + block_size - 1 <= right:
            stat = merge_stats(stat, block_stats[left // block_size])
            left += block_size
        while left <= right:
            stat = add_value(stat, values[left])
            left += 1
        return stat

    half = (window - 1) // 2
    result = values.copy()
    for i, value in enumerate(values):
        left = max(0, i - half)
        right = min(n - 1, i + half)
        count, mean, m2 = range_stats(left, right)
        variance = max(0.0, m2 / count)
        if abs(value - mean) > threshold * math.sqrt(variance):
            result[i] = mean
    return result


def score(values, quality):
    stat = (0, 0.0, 0.0)
    for x in values:
        stat = add_value(stat, x)
    n, mean, m2 = stat
    standard_deviation = math.sqrt(max(0.0, m2 / n))

    quality_weight = (1.0, 0.5, 0.25)
    denominator = sum(quality_weight[q] * abs(x) for x, q in zip(values, quality))
    if denominator == 0.0 or standard_deviation == 0.0:
        return 0

    total = 0.0
    for x, q in zip(values, quality):
        weight = quality_weight[q] * abs(x) / denominator
        total += weight * abs((x - mean) / standard_deviation)
    return math.floor(total * 100 + 0.5)


def solve():
    data = list(map(int, sys.stdin.buffer.read().split()))
    n, window, threshold = data[:3]
    ts, raw, quality = [], [], []
    pointer = 3
    for _ in range(n):
        t, value, q = data[pointer:pointer + 3]
        pointer += 3
        ts.append(t)
        raw.append(value)
        quality.append(q)

    cleaned = clean_values(ts, raw, quality)
    smoothed = smooth_values(cleaned, window, threshold)
    print(score(smoothed, quality))


if __name__ == "__main__":
    solve()

复杂度分析

时间复杂度:$O(n\sqrt n)$;插值、建块、标准化和评分为 $O(n)$,$n$ 个窗口查询各为 $O(\sqrt n)$。

空间复杂度:$O(n)$。

易错点

  1. 无效点只能使用原始质量为 0 的点作为插值锚点。
  2. 窗口统计必须全部读取步骤 1 的同一快照,不能边算边改。
  3. 标准差采用总体口径,分母是窗口长度或 $n$,不是减 1。
  4. sum(x*x)/n - mean*mean 对大基线、小波动数据会数值消减,简单截零不能修复误差。
  5. Python round 使用银行家舍入;题目要求 0.5 向上,应使用 floor(x + 0.5)

第 2 题:模型训练任务资源分配

题目描述

有 $n$ 个训练任务,每个任务必须在两个方案中选择一个。方案 1 消耗资源 $c_1$、耗时 $t_1$;方案 2 消耗资源 $c_2$、耗时 $t_2$。总资源消耗不能超过 $C$。首先最小化总耗时;若总耗时相同,选择总资源消耗更少的方案。

输入描述

第一行输入 $n,C$,满足 $1\le n\le100$、$1\le C\le1000$。

接下来 $n$ 行,每行输入 $c_1,t_1,c_2,t_2$,各数均在 $[1,100]$。保证至少存在一个可行方案。

输出描述

输出三个整数:最小总耗时、对应的最小资源消耗、剩余资源 $C-\text{实际消耗}$。

样例

输入

2 15
5 6 8 3
7 5 9 2

输出

8 14 1

算法

dp[j] 表示处理完当前若干任务、资源恰好消耗 $j$ 时的最小总耗时。初始 dp[0]=0,其余不可达。每处理一个任务,新建数组,分别选择两个方案转移。全部任务完成后,按资源消耗从小到大扫描,取耗时最小的状态;扫描顺序自然完成耗时相同时资源最少的次级目标。

正确性证明

归纳处理任务数。初始状态准确描述零个任务。假设 dp[j] 已准确覆盖前 $i$ 个任务的所有选择,处理第 $i+1$ 个任务时,每个合法方案必须且只能选择方案 1 或方案 2;两类转移枚举全部可能且没有遗漏或重复。因此新数组准确记录前 $i+1$ 个任务在每个精确消耗下的最小耗时。归纳成立。

最终所有不超过 $C$ 的合法方案都位于某个状态中,取最小耗时即得到主目标;在同耗时状态中取最小下标即得到最小资源消耗,故输出正确。

Python ACM 题解

import sys


def solve():
    input = sys.stdin.buffer.readline
    n, capacity = map(int, input().split())
    infinity = 10**30
    dp = [infinity] * (capacity + 1)
    dp[0] = 0

    for _ in range(n):
        c1, t1, c2, t2 = map(int, input().split())
        next_dp = [infinity] * (capacity + 1)
        for used, total_time in enumerate(dp):
            if total_time == infinity:
                continue
            if used + c1 <= capacity:
                next_dp[used + c1] = min(next_dp[used + c1], total_time + t1)
            if used + c2 <= capacity:
                next_dp[used + c2] = min(next_dp[used + c2], total_time + t2)
        dp = next_dp

    best_time = min(dp)
    best_cost = next(cost for cost, time in enumerate(dp) if time == best_time)
    print(best_time, best_cost, capacity - best_cost)


if __name__ == "__main__":
    solve()

复杂度分析

时间复杂度:$O(nC)$。

空间复杂度:$O(C)$。

易错点

  1. 每个任务必须二选一,不能跳过。
  2. 新任务必须从上一层转移,不能原地更新导致同一任务被选择多次。
  3. 状态下标必须表示“恰好消耗”,否则无法处理耗时相同下的资源次级目标。
  4. 最终只考虑 $0\le j\le C$ 的可达状态。