大厂真题 / 华为

华为 2026-10-10 笔试真题 - 研发岗

本场考试概述

考试时间:2026年10月10日

考试岗位:研发岗

难度评级:中等

考点分析:子集枚举与优惠券搜索、多源 BFS、区间覆盖动态规划。

建议策略:先完成多源 BFS,再处理规模较小的优惠券搜索,最后把数轴覆盖转化为前缀状态。优惠券的使用顺序、空凑单列表和覆盖边界都需要单独检查。


第 1 题:凑单与优惠券最优组合

题目描述

需要购买 $n$ 件必买商品,另有 $m$ 件可选商品,每件最多购买一次。给定 $k$ 张优惠券,任选其中若干张并决定使用顺序,求最少实际支付金额。

每张券最多使用一次;使用前的当前价格必须不小于该券门槛 threshold。类型 0 为满减券,将当前价格减去 value;类型 1 为折扣券,将当前价格变为 $\lfloor p\cdot value/100\rfloor$。更新后的价格用于判断下一张券的门槛。最终金额若为负数,输出 0。

输入格式:第一行是 $n,m,k$,其中 $1\le n\le10$、$0\le m\le10$、$0\le k\le5$。第二行给出 $n$ 个必买商品价格,随后给出 $m$ 个可选商品价格;$m=0$ 时没有这一行。商品价格不超过 $1000$。接下来 $k$ 行为 type threshold value,门槛和数值均为正整数,折扣值 90 表示九折。

输出格式:输出一个整数,表示最少支付金额。

样例 1

输入

1 2 2
190
10 20
0 200 30
1 200 90

输出

170

解释:四种购物车总价为 $190,200,210,220$。选择 $10$ 元的凑单商品,总价达到 $200$,使用满减券后支付 $170$。此后价格低于另一张券的门槛,不能继续用券。

样例 2

输入

2 2 2
50 50
30 70
0 100 20
1 150 50

输出

75

解释:只加入 $70$ 元的可选商品,总价为 $170$;先满减得到 $150$,再打五折得到 $75$。若先打折,则得到 $85$,不再满足满减券的 $100$ 元门槛。

思路分析

第一步:枚举购物车。 额外购买商品可能触发更优惠的门槛,不能只比较不凑单的情况。用子集扩展生成全部可选商品总价,再加上必买商品总价。

第二步:枚举券的所有使用前缀。 以当前价格和已用券集合为搜索状态。每到一个状态,都可以立刻停止用券,因此先更新答案;然后尝试每张未使用且满足门槛的券。位掩码只用于记录是否使用,不能仅按掩码保留一个最小价格:更小的当前价格可能失去后续高门槛券的资格。

第三步:检查顺序和结束条件。 样例二购买总价为 170 的商品,先减 20,再打五折,得到 75。反过来使用会改变下一张券是否可用。若价格已经不大于零,最终支付不可能优于零;因为门槛为正,也无法继续使用任何券,可以直接返回。

正确性说明:每个购物子集都会被枚举。对于固定购物车,任意合法优惠券序列均对应搜索树中的一条路径,其每个前缀都包含在停止候选中;搜索只排除不满足门槛或重复使用的券。故所有合法方案的最小支付都被比较。

题解代码

import sys


def minimum_payment(required, optional, coupons):
    totals = [sum(required)]
    for price in optional:
        totals += [total + price for total in totals]
    best = min(totals)

    def search(price, used):
        nonlocal best
        best = min(best, max(0, price))
        if price <= 0:
            return
        for j, (kind, threshold, value) in enumerate(coupons):
            if used >> j & 1 or price < threshold:
                continue
            nxt = price - value if kind == 0 else price * value // 100
            search(nxt, used | (1 << j))

    for total in totals:
        search(total, 0)
    return best


def solve():
    values = list(map(int, sys.stdin.read().split()))
    n, m, k = values[:3]
    pos = 3
    required = values[pos:pos + n]
    pos += n
    optional = values[pos:pos + m]
    pos += m
    coupons = [tuple(values[pos + 3*j:pos + 3*j + 3]) for j in range(k)]
    print(minimum_payment(required, optional, coupons))


if __name__ == "__main__":
    solve()

复杂度分析

时间复杂度:$O(n+2^m\cdot k\cdot k!)$($k\ge1$),每个子集枚举所有排列前缀,每个节点检查至多 $k$ 张券;$k=0$ 时为 $O(n+2^m)$。

空间复杂度:$O(2^m+n+m+k)$,用于子集和、输入数据及深度不超过 $k$ 的递归栈。


第 2 题:病毒扩散模拟

题目描述

一个 $m\times n$ 房间由网格表示:0 为健康人员,1 为已感染人员,x 为障碍。病毒每小时向上下左右相邻的健康格传播一次,障碍不可穿过。

求所有健康人员都被感染需要的最少小时数。若仍有健康人员无法到达,输出 -1;开始时没有健康人员则输出 0。

输入格式:第一行 $m,n$,均在 $[1,100]$ 内;接下来 $m$ 行,每行 $n$ 个以空格分隔的网格标记。

输出格式:输出最少小时数,或 -1。

样例 1

输入

3 4
0 0 0 0
1 x 0 0
0 0 x 1

输出

3

解释:两个感染源同时出发,各格的最早感染时刻如下。第一行第三列最晚感染,因此答案为 $3$。

1 2 3 2
0 x 2 1
1 2 x 0

样例 2

输入

3 3
0 x 0
x x 0
1 0 0

输出

-1

解释:左上角健康人员的两个相邻格均为障碍,永远无法被感染。即使其余健康人员都可被感染,仍应输出 $-1$。

思路分析

第一步:把所有感染源同时入队。 分别从每个感染源搜索会重复遍历房间。所有初始感染格的感染时刻都是零,将它们一起放入 FIFO 队列,就能按时间层次统一扩散。

第二步:首次入队就标记感染。 从时刻 $t$ 的格子走向健康邻格,该邻格的感染时刻为 $t+1$。入队时立即把它从健康改为感染,避免同一格被多个感染源重复加入。队列按时刻非递减弹出,第一次到达就是最短传播时间。

第三步:维护剩余健康人数。 每感染一人,计数减一。遍历结束后仍有剩余,说明存在被障碍隔开的区域或没有感染源,输出 -1;否则返回最大的感染时刻。无健康人员时答案初值零即可覆盖。

正确性说明:把非障碍格连成无权图,多源 BFS 得到每个格子到任意感染源的最短距离。所有健康格的这些距离的最大值,就是完成传播的最早时刻;无法访问的健康格对应无法完成的情况。

题解代码

import sys
from collections import deque


def infection_time(grid):
    m, n = len(grid), len(grid[0])
    queue = deque()
    healthy = 0
    for i in range(m):
        for j in range(n):
            if grid[i][j] == "1":
                queue.append((i, j, 0))
            elif grid[i][j] == "0":
                healthy += 1
    answer = 0
    while queue:
        i, j, hour = queue.popleft()
        for di, dj in ((1, 0), (-1, 0), (0, 1), (0, -1)):
            ni, nj = i + di, j + dj
            if 0 <= ni < m and 0 <= nj < n and grid[ni][nj] == "0":
                grid[ni][nj] = "1"
                healthy -= 1
                answer = hour + 1
                queue.append((ni, nj, hour + 1))
    return answer if healthy == 0 else -1


def solve():
    input = sys.stdin.readline
    m, n = map(int, input().split())
    grid = [input().split() for _ in range(m)]
    print(infection_time(grid))


if __name__ == "__main__":
    solve()

复杂度分析

时间复杂度:$O(mn)$,每个格子最多入队一次,每次只检查四个方向。

空间复杂度:$O(mn)$,用于网格和队列。


第 3 题:多规格炸弹覆盖

题目描述

数轴上有 $N$ 栋建筑,位置互不相同。炸弹只能部署在建筑处,有 $M$ 种规格,每种规格可使用任意多颗。规格为 $r$ 的炸弹消耗 $r$ 单位资源,覆盖与部署点距离不超过 $r$ 的所有建筑。

求覆盖所有建筑的最小资源消耗。

输入格式:第一行是 $N,M$,满足 $1\le N\le100$、$1\le M\le10$。第二行给出 $N$ 个不同的整数位置,范围为 $[0,10^9]$;第三行给出 $M$ 个不同的整数规格,范围为 $[1,10^9]$。

输出格式:输出最小总消耗。

样例 1

输入

3 3
0 20 80
15 20 40

输出

35

解释:在位置 $0$ 部署规格 $20$ 的炸弹,覆盖位置 $0,20$;在位置 $80$ 部署规格 $15$ 的炸弹,总费用为 $20+15=35$。距离等于规格也算覆盖。

样例 2

输入

9 3
4 2 0 100 110 105 200 250 300
50 2 5

输出

13

解释:在位置 $2,200,250,300$ 各部署一颗规格 $2$ 的炸弹,其中位置 $2$ 的炸弹覆盖 $0,2,4$;在位置 $105$ 部署规格 $5$ 的炸弹,覆盖 $100,105,110$。总费用为 $4\times2+5=13$。

思路分析

第一步:枚举一颗炸弹能覆盖的区间。 建筑排序后,每颗炸弹覆盖一个连续下标区间 $[L,R)$。枚举每个部署点和每种规格,二分求出该区间;端点距离等于规格也被覆盖,因此右边界使用 bisect_right。

第二步:定义已覆盖前缀。 令 $dp[i]$ 为覆盖前 $i$ 栋建筑的最小代价,初始 $dp[0]=0$。当一颗炸弹满足 $L\le i<R$ 时,它覆盖了第一栋尚未覆盖的建筑 $i$,且能把连续覆盖前缀推进到 $R$,于是有:

\[dp[R]=\min(dp[R],dp[i]+r).\]

按 $i$ 从小到大处理。每次转移都会严格增加前缀长度,不存在循环依赖。

第三步:证明不会漏掉允许重叠的方案。 任意全覆盖方案都包含一颗覆盖当前第一栋未覆盖建筑的炸弹;加入它后,已有前缀与该区间没有空隙,可以推进到它的右端。重复这一过程即可从该方案中取出一条到达 $N$ 的转移链。被取出的炸弹是原方案的子集,费用不会增加。反过来,每条转移链都给出真实可部署且覆盖全部建筑的方案,故 DP 的最小值正是最优值。

这一状态不要求各颗炸弹的覆盖范围互不相交,也不要求部署点是第一个未覆盖的建筑。样例二可以在位置 2 使用规格 2,同时覆盖 0,2,4。

题解代码

import sys
from bisect import bisect_left, bisect_right


def minimum_resource(positions, radii):
    positions = sorted(positions)
    n = len(positions)
    intervals = []
    for center in positions:
        for radius in radii:
            left = bisect_left(positions, center - radius)
            right = bisect_right(positions, center + radius)
            intervals.append((left, right, radius))
    dp = [float("inf")] * (n + 1)
    dp[0] = 0
    for i in range(n):
        for left, right, cost in intervals:
            if left <= i < right:
                dp[right] = min(dp[right], dp[i] + cost)
    return dp[n]


def solve():
    data = list(map(int, sys.stdin.read().split()))
    n, m = data[:2]
    print(minimum_resource(data[2:2 + n], data[2 + n:2 + n + m]))


if __name__ == "__main__":
    solve()

复杂度分析

时间复杂度:$O(N^2M+NM\log N)$,预处理 $NM$ 个区间,随后每个前缀遍历全部区间。

空间复杂度:$O(NM+N+M)$,保存区间、输入与前缀状态。

小结

  • 凑单题的规模允许全枚举,用券门槛依赖当前价格,因此顺序必须进入搜索。
  • 病毒扩散用多源 BFS 同时模拟所有起点,入队时标记避免重复。
  • 数轴覆盖可以按第一处未覆盖位置推进前缀,不必枚举所有炸弹组合。