大厂真题 / 华为

华为 8.26 笔试真题 - 研发岗

本场考试概述

考试时间:2026 年 8 月 26 日

考试岗位:研发岗

难度评级:中等

考点分析

  1. 居民区工厂选址:曼哈顿距离拆维 + 带权中位数。
  2. 机器人清洁系统:只能向右或向下的网格路径动态规划。
  3. 小鲤鱼荒岛逃生:BFS 最短路 + 多级比较规则下的路径还原。

前两题的重点是识别经典模型;第三题除了求最短距离,还要依次处理出口坐标、路径行坐标和、路径列坐标和四级比较条件。


第 1 题:居民区工厂选址

题目描述

二维平面上有 $n$ 个居民区,第 $i$ 个居民区位于 $({x_i},{y_i})$,共有 $p_i$ 名居民。现在选择一个点 $(X,Y)$ 建设工厂,使所有居民到工厂的加权曼哈顿距离之和最小。

第 $i$ 个居民区产生的代价为:

\[p_i\bigl(\lvert x_i-X\rvert+\lvert y_i-Y\rvert\bigr).\]

求最小总代价。

输入描述

第一行输入整数 $n$,满足 $1\le n\le 10^4$。

接下来 $n$ 行,每行输入三个整数 $x_i,y_i,p_i$,满足 $-10^9\le x_i,y_i\le 10^9$,$1\le p_i\le 10^4$。

输出描述

输出最小的加权曼哈顿距离之和。

样例

输入

3
1 2 3
4 5 2
7 8 1

输出

24

思路分析

曼哈顿距离可以按横、纵坐标拆开:

\[\sum_{i=1}^{n}p_i\lvert x_i-X\rvert+\sum_{i=1}^{n}p_i\lvert y_i-Y\rvert.\]

两个部分没有公共变量,因此分别求出一维最优解再相加即可。

考虑一维问题。把所有点按坐标排序,总权重记为 $W$。当选址从左向右移动时,左侧点的距离增大,右侧点的距离减小;一旦左侧累计权重达到总权重的一半,继续右移就不会让总代价更小。因此,首个满足累计权重至少为 $\lceil W/2\rceil$ 的坐标就是带权中位数。

横、纵坐标分别排序并求带权中位数,再扫描所有点累加距离即可。坐标差与总权重的乘积可能达到 $10^{17}$ 量级,计算过程需要使用 64 位整数;Python 整数可直接处理。

题解代码

import sys


def minimum_cost_1d(points):
    points.sort()
    total_weight = sum(weight for _, weight in points)
    target = (total_weight + 1) // 2

    prefix_weight = 0
    median = points[0][0]
    for coordinate, weight in points:
        prefix_weight += weight
        if prefix_weight >= target:
            median = coordinate
            break

    return sum(weight * abs(coordinate - median) for coordinate, weight in points)


def solve():
    input = sys.stdin.buffer.readline
    n = int(input())
    x_points = []
    y_points = []

    for _ in range(n):
        x, y, population = map(int, input().split())
        x_points.append((x, population))
        y_points.append((y, population))

    print(minimum_cost_1d(x_points) + minimum_cost_1d(y_points))


if __name__ == "__main__":
    solve()

复杂度分析

时间复杂度:$O(n\log n)$,主要开销是分别对横、纵坐标排序。

空间复杂度:$O(n)$,用于保存两个维度的坐标和权重。

易错点

  1. 普通中位数不考虑居民数量,本题必须求带权中位数。
  2. 横、纵坐标应分别处理,但使用同一个居民数量作为权重。
  3. 固定宽度语言中需要使用 64 位整数保存答案。

第 2 题:机器人清洁系统

题目描述

机器人位于一个 $m\times n$ 的网格房间,需要从左上角 $(0,0)$ 移动到右下角 $(m-1,n-1)$,每次只能向右或向下移动一格。

每个格子有一个非负整数,表示该处的灰尘浓度。机器人从浓度为 $a$ 的格子移动到浓度为 $b$ 的相邻格子时,需要消耗 $\lvert b-a\rvert$ 单位调节能量。求所有合法路径中的最小总能耗。

输入描述

第一行输入两个整数 $m,n$,满足 $1\le m,n\le 200$。

接下来 $m$ 行,每行输入 $n$ 个整数,表示网格中的灰尘浓度,数值范围为 $[0,10000]$。

输出描述

输出从左上角到右下角的最小总调节能耗。

样例

输入

1 5
1 2 3 4 5

输出

4

思路分析

每一步的能耗只取决于当前格和前一个格,不依赖更早的移动过程,因此可以使用动态规划。

dp[j] 表示处理到当前行时,从起点走到当前列格子的最小能耗。对格子 $(i,j)$:

  • 从上方到达时,候选值是原 dp[j] 加上下两个格子的浓度差;
  • 从左侧到达时,候选值是更新后的 dp[j-1] 加左右两个格子的浓度差。

取两个合法来源中的较小值。按从上到下、从左到右的顺序更新,就能把二维状态压缩为一行。单行、单列和 $1\times1$ 网格都能由同一套边界逻辑处理。

题解代码

import sys


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

    infinity = 10**30
    dp = [infinity] * n
    dp[0] = 0

    for i in range(m):
        for j in range(n):
            if i == 0 and j == 0:
                continue

            best = infinity
            if i > 0:
                best = dp[j] + abs(grid[i][j] - grid[i - 1][j])
            if j > 0:
                from_left = dp[j - 1] + abs(grid[i][j] - grid[i][j - 1])
                best = min(best, from_left)
            dp[j] = best

    print(dp[-1])


if __name__ == "__main__":
    solve()

复杂度分析

时间复杂度:$O(mn)$,每个格子只进行常数次计算。

空间复杂度:$O(n)$,滚动数组只保存当前行的状态;输入网格占用的存储不计入额外空间。

易错点

  1. 起点不产生移动能耗,初始值应为 0
  2. 更新当前行时,dp[j] 仍代表上方状态,而 dp[j-1] 已代表左侧状态。
  3. 移动代价是相邻格数值之差的绝对值,不是目标格自身的数值。

第 3 题:小鲤鱼荒岛逃生

题目描述

给定一个 $m\times n$ 的岛屿地图:0 表示水域,1 表示陆域。小鲤鱼从指定位置出发,只能在水域中向上、下、左、右移动。如果到达矩阵边界上的水域格,就能逃回河流。

需要输出最短逃生路径。如果存在多条候选路径,按以下优先级选择:

  1. 移动步数更少;
  2. 最终出口坐标按行、列字典序更小;
  3. 路径上所有点的行坐标之和更小;
  4. 路径上所有点的列坐标之和更小。

题目保证无需处理以上四项全部相同但路径仍不同的情况。

输入描述

第一行输入两个整数 $m,n$,满足 $3\le m\le 20$,$3\le n\le 30$。

第二行输入起点坐标 $p_0,p_1$,坐标从 0 开始。

接下来 $m$ 行,每行输入 $n$ 个 01

输出描述

如果无法逃生,输出 -1

否则第一行输出最少移动步数 $d$,随后输出 $d+1$ 行路径坐标,其中第一行为起点,最后一行为出口。

样例

输入

4 5
1 1
1 1 1 1 1
1 0 0 1 1
1 1 0 0 0
1 0 1 1 1

输出

4
1 1
1 2
2 2
2 3
2 4

思路分析

所有移动的代价都是 1,因此先从起点执行 BFS,得到每个可达水域格的最短距离 dist。在所有可达边界格中,按 (距离, 行, 列) 取最小值,就确定了题目规则中的最短步数和出口坐标。

出口确定后,还要在所有通向该出口的最短路径中比较坐标和。BFS 距离把图划分成若干层,一条最短路径只能从距离为 $d-1$ 的格子走向距离为 $d$ 的格子。

对每个格子 $v$,记录从起点到它的最优 (行坐标和, 列坐标和),并保存对应前驱。处理 $v$ 时,只枚举满足 dist[u] + 1 == dist[v] 的相邻格 $u$。所有候选都会加上同一个当前坐标,所以直接选前驱累计坐标和字典序最小者即可。按照 BFS 发现顺序处理时,前一层状态必定已经完成。

最后从出口沿前驱回溯,再反转得到完整路径。若起点是陆域,或起点所在水域与任意边界都不连通,则输出 -1

题解代码

import sys
from collections import deque


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

    if grid[start_row][start_col] == 1:
        print(-1)
        return

    directions = ((-1, 0), (1, 0), (0, -1), (0, 1))
    dist = [[-1] * n for _ in range(m)]
    queue = deque([(start_row, start_col)])
    order = [(start_row, start_col)]
    dist[start_row][start_col] = 0

    while queue:
        row, col = queue.popleft()
        for delta_row, delta_col in directions:
            next_row = row + delta_row
            next_col = col + delta_col
            if not (0 <= next_row < m and 0 <= next_col < n):
                continue
            if grid[next_row][next_col] == 1 or dist[next_row][next_col] != -1:
                continue
            dist[next_row][next_col] = dist[row][col] + 1
            queue.append((next_row, next_col))
            order.append((next_row, next_col))

    exits = []
    for row, col in order:
        if row == 0 or row == m - 1 or col == 0 or col == n - 1:
            exits.append((dist[row][col], row, col))

    if not exits:
        print(-1)
        return

    _, target_row, target_col = min(exits)

    best_sum = [[None] * n for _ in range(m)]
    parent = [[None] * n for _ in range(m)]
    best_sum[start_row][start_col] = (start_row, start_col)

    for row, col in order[1:]:
        best_candidate = None
        best_parent = None
        for delta_row, delta_col in directions:
            prev_row = row + delta_row
            prev_col = col + delta_col
            if not (0 <= prev_row < m and 0 <= prev_col < n):
                continue
            if dist[prev_row][prev_col] != dist[row][col] - 1:
                continue

            prev_sum = best_sum[prev_row][prev_col]
            candidate = (prev_sum[0] + row, prev_sum[1] + col)
            if best_candidate is None or candidate < best_candidate:
                best_candidate = candidate
                best_parent = (prev_row, prev_col)

        best_sum[row][col] = best_candidate
        parent[row][col] = best_parent

    path = []
    current = (target_row, target_col)
    while current is not None:
        path.append(current)
        row, col = current
        current = parent[row][col]
    path.reverse()

    print(len(path) - 1)
    for row, col in path:
        print(row, col)


if __name__ == "__main__":
    solve()

复杂度分析

时间复杂度:$O(mn)$,BFS 和分层前驱转移都只检查每个格子的四个方向。

空间复杂度:$O(mn)$,用于保存距离、累计坐标和、前驱与队列。

易错点

  1. 题目坐标从 0 开始,输出时不要额外加一。
  2. 不能只依赖 BFS 邻居访问顺序决定路径,多级比较规则必须显式计算。
  3. 选择出口时先比较距离,再比较出口的行、列;路径坐标和只在出口确定后参与比较。
  4. 输出的步数不包含起点,但路径坐标包含起点,因此共输出 d + 1 个坐标。

小结

  • 第一题利用曼哈顿距离可拆维的性质,把二维选址转化为两个一维带权中位数问题。
  • 第二题识别每步代价只依赖相邻格,使用滚动数组完成网格路径动态规划。
  • 第三题先用 BFS 固定最短距离与出口,再在最短路分层图上按坐标和选择前驱。