大厂真题 / 华为

华为 4.29 笔试真题 - 研发岗

本场考试概述

考试时间:2026年4月29日 考试岗位:研发岗(国内) 难度评级:中等偏难

考点分析

  1. 第一题:哈希计数 + 后缀和(难度中等)
  2. 第二题:二叉树层序遍历 + 自底向上累加(难度简单)
  3. 第三题:分治最近点对(难度困难)

建议策略

  • 第一题和第二题相对基础,确保拿满分
  • 第三题分治最近点对是竞赛经典算法,建议提前掌握模板,考场上能快速默写
  • 注意第一题的去重处理和第三题的精度截断(向下取整,不是四舍五入)

第一题:电商购物记录商品关联分析

题目描述

某电商平台希望分析用户的购买行为,找出经常一起购买的商品对。商品对指两个不同商品 ID 在同一用户的购物记录中同时出现。

给定 $n$ 个用户的购物记录和 $q$ 个查询,每个查询给出阈值 $t$,要求输出共现频率 $\geq t$ 的商品对数量。

输入描述

第一行包含整数 $n, q$,表示用户数量和查询数量。接下来 $n$ 行,每行第一个整数 $k$ 表示该用户购买的商品数量,接下来 $k$ 个整数表示商品 ID。同一用户的商品 ID 可能重复,需去重处理。接下来 $q$ 行,每行一个整数 $t$,查询共现频率 $\geq t$ 的商品对数量。

输出描述

共 $q$ 行,每行输出一个整数,表示满足条件的商品对数量。

样例

输入

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

输出

4
1

样例解释:对每个用户的购物记录去重后生成商品对,统计各商品对出现次数:$(1,2)$ 出现 1 次,$(1,3)$ 出现 2 次(用户 1 和用户 5),$(2,3)$ 出现 1 次,$(2,4)$ 出现 1 次。查询 $t=1$ 时共 4 对满足,查询 $t=2$ 时仅 $(1,3)$ 满足。

思路分析

第一步:理解商品对的共现

每个用户有一组商品(去重后),从中选出所有二元组合就构成了该用户产生的商品对。一个商品对的”共现频率”就是有多少个用户同时买了这两件商品。

第二步:暴力枚举商品对

关键观察:题目没有给出明确的数据范围约束,但从实际场景推断,单个用户的去重商品数有限。假设每个用户最多有 $k$ 种不同商品,则每个用户产生 $\binom{k}{2}$ 个商品对。对所有用户的商品对用哈希表累加频次即可。

为了保证商品对的唯一表示,对每个用户的商品去重后排序,然后双重循环枚举所有 $(a, b)$ 对($a < b$),以 $(a, b)$ 为 key 在哈希表中 +1。

第三步:后缀和加速多次查询

统计完所有商品对的频次后,建立计数数组 $\text{cnt}[f]$ = 频次恰好等于 $f$ 的商品对数量。从后往前做后缀和:$\text{cnt}[f] += \text{cnt}[f+1]$,这样 $\text{cnt}[t]$ 就表示频次 $\geq t$ 的商品对数量,每个查询 $O(1)$ 回答。

题解代码

import sys
from collections import defaultdict

input = sys.stdin.readline

def solve():
    n, q = map(int, input().split())

    freq = defaultdict(int)

    for _ in range(n):
        line = list(map(int, input().split()))
        k = line[0]
        items = sorted(set(line[1:k + 1]))
        for i in range(len(items)):
            for j in range(i + 1, len(items)):
                freq[(items[i], items[j])] += 1

    # cnt[f] = 频次恰好等于 f 的商品对数量
    cnt = [0] * (n + 2)
    for c in freq.values():
        cnt[c] += 1

    # 后缀和:cnt[f] 变为频次 >= f 的商品对数量
    for i in range(n, 0, -1):
        cnt[i - 1] += cnt[i]

    out = []
    for _ in range(q):
        t = int(input())
        out.append(str(cnt[t] if t <= n else 0))
    print('\n'.join(out))

solve()

复杂度分析

时间复杂度:$O(\sum k_i^2 + n + q)$,枚举阶段每个用户 $O(k_i^2)$,后缀和 $O(n)$,查询 $O(q)$

空间复杂度:$O(P + n)$,$P$ 为不同商品对数量


第二题:文件目录的分层压缩

题目描述

有一个 $n$ 层目录的文件系统,每个目录有至多两个子目录(完全二叉树结构)。需要统计每个目录(含子目录)的总大小。

一个目录的总大小等于自身文件大小加上所有子目录的总大小。

输入描述

第一行包含整数 $n$,表示目录的层数。第二行为按二叉树层序遍历给出的第 1 到第 $n$ 层各目录文件的大小,$-1$ 表示该节点为空,$0$ 表示该节点无文件但可能有子目录。

输出描述

按二叉树层序遍历输出统计后的每个目录总大小,空节点用 $-1$ 表示。最后一层末尾连续的 $-1$ 应省略不输出。

样例

输入

3
1 2 -1 5 3

输出

11 10 -1 5 3

样例解释:节点 5 和 3 无子节点。节点 2 有两个子节点 5 和 3,总大小为 $2+5+3=10$。根节点只有左子节点(总大小 10),总大小为 $1+10=11$。

思路分析

第一步:理解数据结构

输入是一棵二叉树按层序铺成的一维数组,下标从 0 开始。$n$ 层二叉树最多 $2^n - 1$ 个节点。这和堆的数组表示完全一致:下标 $i$ 的左孩子在 $2i+1$,右孩子在 $2i+2$。

第二步:自底向上累加

要计算每个节点的子树总和,只需从数组末尾向前遍历:处理父节点时,子节点的值已经是各自子树的累加结果。

具体做法:从 $\lfloor \text{len}/2 \rfloor - 1$ 开始倒序到 0(这是最后一个非叶子节点的下标)。对每个非空节点,加上其非空孩子的值。

以样例 1 2 -1 5 3 为例:

  • 数组下标:0→1, 1→2, 2→-1, 3→5, 4→3
  • 从下标 1 开始(最后一个非叶子节点):$\text{tree}[1] = 2 + 5 + 3 = 10$
  • 下标 0:左孩子 $\text{tree}[1]=10$,右孩子 $\text{tree}[2]=-1$ 跳过,$\text{tree}[0] = 1 + 10 = 11$
  • 结果:11 10 -1 5 3

第三步:输出裁剪

末尾连续的 $-1$ 省略不输出。

题解代码

import sys

input = sys.stdin.readline

def solve():
    n = int(input())
    max_nodes = (1 << n) - 1
    tree = [-1] * max_nodes

    vals = list(map(int, input().split()))
    for i in range(len(vals)):
        tree[i] = vals[i]

    # 从最后一个非叶子节点开始,倒序累加子节点
    for i in range(max_nodes // 2 - 1, -1, -1):
        if tree[i] == -1:
            continue
        left = 2 * i + 1
        right = 2 * i + 2
        if left < max_nodes and tree[left] != -1:
            tree[i] += tree[left]
        if right < max_nodes and tree[right] != -1:
            tree[i] += tree[right]

    # 去掉末尾连续的 -1
    last = len(vals) - 1
    while last >= 0 and tree[last] == -1:
        last -= 1

    print(' '.join(str(tree[i]) for i in range(last + 1)))

solve()

复杂度分析

时间复杂度:$O(2^n)$,遍历所有节点一次

空间复杂度:$O(2^n)$,存储层序数组


第三题:无人机的安全距离检测

题目描述

某活动现场有 $n$ 架无人机,每架无人机有固定坐标 $(x, y)$。计算所有无人机之间的最小欧式距离,仅保留整数部分(向下截断,不四舍五入)。如果只有 1 架无人机,输出 $-1$。

样例

输入

5
1 1
10 18
17 50
22 25
39 50

输出

13

样例解释:坐标 $(10,18)$ 和 $(22,25)$ 两点的欧式距离最小,约为 $13.89$,直接截断小数部分输出 $13$。

思路分析

第一步:分析暴力法的瓶颈

最直接的方法是两两枚举 $n$ 个点,计算距离取最小值,时间复杂度 $O(n^2)$。当 $n$ 较大时(如 $10^5$),暴力法会超时。

第二步:分治最近点对算法

这是计算几何中的经典问题,分治算法可以将复杂度降到 $O(n \log^2 n)$。核心思想:

  1. 排序 + 分割:先将所有点按 $x$ 坐标排序,从中间位置一分为二
  2. 递归求解:递归求左半部分最近距离 $d_L$ 和右半部分最近距离 $d_R$,取较小值 $d = \min(d_L, d_R)$
  3. 跨区域检查:全局最近点对可能一个在左半、一个在右半。但这种跨区域点对的两个点到分割线的 $x$ 距离一定都小于 $d$——否则光 $x$ 方向的差值就已经 $\geq d$ 了,不可能比 $d$ 更近。所以只需要检查分割线附近宽度为 $2d$ 的竖直条带内的点
  4. 条带扫描:条带内的点按 $y$ 坐标排序,对每个点只需向上检查 $y$ 差小于 $d$ 的点。可以证明每个点最多检查常数个邻居(约 6-8 个),所以条带内检查是 $O(n)$ 的

第三步:实现要点

  • 用距离的平方来比较,避免浮点运算误差,最后才开方
  • 点数 $\leq 3$ 时直接暴力
  • 最终输出要用 int(math.sqrt(d2)) 向下截断

题解代码

import sys
import math

input = sys.stdin.readline

def dist2(a, b):
    return (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2

def closest_pair(pts, l, r):
    # 点数 <= 3 时暴力
    if r - l <= 3:
        min_d = float('inf')
        for i in range(l, r):
            for j in range(i + 1, r):
                min_d = min(min_d, dist2(pts[i], pts[j]))
        return min_d

    mid = (l + r) // 2
    mid_x = pts[mid][0]
    d = min(closest_pair(pts, l, mid), closest_pair(pts, mid, r))

    # 筛选 x 距分割线不超过 sqrt(d) 的点
    strip = []
    for i in range(l, r):
        dx = pts[i][0] - mid_x
        if dx * dx < d:
            strip.append(pts[i])

    # 按 y 排序后只检查 y 相邻的点
    strip.sort(key=lambda p: p[1])

    for i in range(len(strip)):
        for j in range(i + 1, len(strip)):
            dy = strip[j][1] - strip[i][1]
            if dy * dy >= d:
                break
            d = min(d, dist2(strip[i], strip[j]))

    return d

def solve():
    n = int(input())
    if n == 1:
        input()
        print(-1)
        return

    pts = []
    for _ in range(n):
        x, y = map(int, input().split())
        pts.append((x, y))

    pts.sort()
    d2 = closest_pair(pts, 0, n)
    print(int(math.sqrt(d2)))

solve()

复杂度分析

时间复杂度:$O(n \log^2 n)$,分治共 $\log n$ 层,每层对条带内点按 $y$ 排序耗费 $O(n \log n)$

空间复杂度:$O(n)$,存储点坐标和临时数组


小结

  • 第一题考查哈希计数 + 后缀和,核心在于对每个用户的商品去重排序后枚举所有二元组合,再用后缀和将多次查询优化到 $O(1)$
  • 第二题是基础的二叉树层序数组操作,利用父子下标关系自底向上累加即可,属于送分题
  • 第三题是经典的分治最近点对,属于竞赛级难题,需要提前掌握分治框架和条带扫描技巧,注意最终结果向下截断