大厂真题 / 华为

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

本场考试概述

考试时间:2026年10月28日

考试岗位:研发岗

难度评级:中等

考点分析:

  • 第一题:有向图路径计数、分步动态规划(中等)。
  • 第二题:稀疏表示、大整数加法、进位模拟(中等)。
  • 第三题:资金约束下的状态机动态规划(中等偏难)。

建议策略:

  • 第一题以“走了多少条边”为阶段,严格只从上一层状态转移,允许重复经过节点。
  • 第二题按指数存储非零数字位,进位只向更高指数传播,避免按最高指数开数组。
  • 第三题分别记录空仓和持仓资金,并让每天的状态只从前一天转移,避免同日买卖。

第 1 题:物流中转网络

题目描述

有 $n$ 个中转站,编号为 $1$ 到 $n$,由 $m$ 条单向运输线路连接。给定起点 start、终点 target 和整数 $k$,求从起点到终点恰好经过 $k$ 条线路的不同路径数量。路径允许重复经过同一中转站,但同一对站点之间没有重边。

输入格式与约束

  • 第一行包含 $n,m$,满足 $1\le n\le20$、$1\le m\le200$。
  • 第二行包含 start target k,其中 $1\le start,target\le n$、$1\le k\le10$。
  • 接下来 $m$ 行,每行包含一条有向边 from to,满足 $1\le from,to\le n$ 且 from 与 to 不同。

输出格式

输出恰好经过 $k$ 条线路的路径数;不存在符合条件的路径时输出 $0$。

样例 1

输入

4 6
1 4 4
1 2
2 1
2 3
3 2
3 4
2 4

输出

2

解释:符合条件的路径是 $1\to2\to1\to2\to4$ 和 $1\to2\to3\to2\to4$。

样例 2

输入

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

输出

2

解释:恰好经过两条线路的路径为 $1\to2\to4$ 和 $1\to3\to4$。

样例 3

输入

3 3
1 3 1
1 2
2 3
1 3

输出

1

解释:只经过一条线路时,只有直接从中转站 $1$ 到 $3$ 的路径符合要求。

思路分析

第一步:把“恰好走 $k$ 步”作为动态规划阶段。

令 dp[v] 表示当前阶段从起点出发、走了指定条数线路后到达站点 $v$ 的路径数。走 $0$ 条线路时,只有起点可达,因此 dp[start] = 1,其余状态为 $0$。

第二步:沿每条有向边推进一层。

若边为 $u\to v$,当前所有到达 $u$ 的路径再走这条边,便成为下一层到达 $v$ 的路径。因此对每条边执行 next_dp[v] += dp[u]。每一轮都从旧数组创建新数组,完成整轮后再替换状态,确保每次转移恰好增加一条线路。

题目允许重复经过站点,所以不应排除已经访问过的点。不同路径由所选线路的顺序区分;逐层沿边累加会把每条长度为 $k$ 的线路序列恰好计数一次。

第三步:读取第 $k$ 层的目标状态。

重复上述过程 $k$ 次后,dp[target] 即为答案。暴力枚举路径可能随 $k$ 指数增长;这里每一层只扫描 $m$ 条边,在给定的小范围内足够高效。

正确性证明

走 $0$ 条线路时,到达起点的空路径恰好有一条,到达其他站点的路径数为零,初始化正确。

假设当前 dp[u] 正确统计走了 $i$ 条线路后到达 $u$ 的路径数。每条长度为 $i+1$ 且终点为 $v$ 的路径,都唯一地由一条长度为 $i$、终点为某个 $u$ 的路径及最后一条边 $u\to v$ 组成。对所有边执行累加会完整且不重复地构造这些路径。因此下一层数组正确统计走 $i+1$ 条线路的路径数。

归纳可知,执行 $k$ 轮后 dp[target] 正是题目所求路径数。

题解代码

import sys
input = sys.stdin.readline


def count_paths(n, edges, start, target, steps):
    dp = [0] * (n + 1)
    dp[start] = 1
    for _ in range(steps):
        next_dp = [0] * (n + 1)
        for source, destination in edges:
            next_dp[destination] += dp[source]
        dp = next_dp
    return dp[target]


def solve():
    n, m = map(int, input().split())
    start, target, steps = map(int, input().split())
    edges = [tuple(map(int, input().split())) for _ in range(m)]
    print(count_paths(n, edges, start, target, steps))


if __name__ == '__main__':
    solve()

复杂度分析

时间复杂度:$O(km)$。每一层遍历全部 $m$ 条线路,共推进 $k$ 层。

空间复杂度:$O(n+m)$。存储边以及当前层和下一层的路径计数。


第 2 题:大数加法

题目描述

输入两个用稀疏编码表示的非负整数,输出它们的和,仍使用相同编码。

每个非零片段写成 xxx:n,表示整数 $xxx\times10^n$。片段按高位到低位排列,xxx 由一至三位数字组成且不包含 $0$;连续的非零数字尽量组成最多三位的片段。整个整数以 0:0 结束,零本身也写作 0:0。

例如,整数 $1234005006$ 编码为 123:7 4:6 5:3 6:0 0:0。

输入格式与约束

  • 输入两行,每行是一个非负整数的稀疏编码。
  • 每个片段的指数 $n$ 满足 $0\le n<2{,}000{,}000{,}000$;系数片段长度不超过 $3$,且内部不含 $0$。
  • 单个编码字符串长度小于 $512$,两个数的和的最高指数小于 $2{,}000{,}000{,}000$。
  • 每个整数的编码均以 0:0 结束。

输出格式

输出两数之和的稀疏编码,按格式以 0:0 结尾。

样例 1

输入

12:6 345:2 0:0
1:7 965:3 49:1 0:0

输出

229:5 999:2 9:1 0:0

解释:两个输入数分别为 $12{,}034{,}500$ 和 $10{,}965{,}490$,和为 $22{,}999{,}990$,对应编码 229:5 999:2 9:1 0:0。

样例 2

输入

123:5 9:2 0:0
878:5 1:2 1:0 0:0

输出

1:8 1:5 1:3 1:0 0:0

解释:两数分别为 $12{,}300{,}900$ 和 $87{,}800{,}101$,和为 $100{,}101{,}001$。结果中非零数字位彼此不连续,所以需要分别编码。

思路分析

第一步:将片段展开为稀疏的数字位映射。

使用字典 digits,以十进制指数为键、该位的数字和为值。对于 abc:e,字符从右向左依次对应指数 $e,e+1,e+2$。解析两行时,相同指数的数字累加。指数可能接近 $2\times10^9$,因此不能创建从 $0$ 到最高指数的数组。

第二步:从低位向高位处理进位。

将出现过的指数放入最小堆,从小到大处理。当前位的值用 divmod(value, 10) 分成余数和进位;余数保留在当前指数,进位加到下一个指数。如果进位目标是原先未出现的新指数,也将它加入堆。这样既能处理连续进位,也不会遍历指数之间可能极大的空白区间。

第三步:按编码规则重新组合。

删除值为 $0$ 的数字位,把剩余数字按指数从高到低排列。只有指数连续递减的位才能拼在同一片段中,每段至多三位;段中的数字天然都非零。若结果没有非零位,输出 0:0,否则在所有片段后追加 0:0。

正确性证明

解码时,每个片段的每个数字都放到其实际对应的十进制指数上;相同指数求和与十进制逐位加法等价,因此字典准确表示两个输入数的位和。

堆按指数从小到大处理。处理某指数前,所有更低指数产生的进位都已加入该位;divmod 恰好留下该位的十进制数字并把剩余部分传给下一位。新产生的指数也进入堆,故进位不会遗漏,所有最终非零数字位均正确。

最后按连续指数分组且每组最多三位,所得片段满足编码规则;每位只出现一次,因而编码表示的数与逐位加法结果相同。若没有非零位,和为零,输出约定的 0:0。

题解代码

import sys
import heapq
input = sys.stdin.readline


def decode(encoded):
    digits = {}
    for token in encoded.split():
        if token == '0:0':
            break
        coefficient, exponent_text = token.split(':')
        exponent = int(exponent_text)
        for offset, char in enumerate(reversed(coefficient)):
            position = exponent + offset
            digits[position] = digits.get(position, 0) + int(char)
    return digits


def add_sparse(first, second):
    digits = decode(first)
    for position, value in decode(second).items():
        digits[position] = digits.get(position, 0) + value

    pending = list(digits)
    heapq.heapify(pending)
    queued = set(pending)
    while pending:
        position = heapq.heappop(pending)
        value = digits[position]
        carry, digit = divmod(value, 10)
        digits[position] = digit
        if carry:
            next_position = position + 1
            digits[next_position] = digits.get(next_position, 0) + carry
            if next_position not in queued:
                heapq.heappush(pending, next_position)
                queued.add(next_position)

    nonzero = sorted((position, digit)
                     for position, digit in digits.items() if digit)
    if not nonzero:
        return '0:0'

    pieces = []
    i = len(nonzero) - 1
    while i >= 0:
        highest = nonzero[i][0]
        coefficient = [str(nonzero[i][1])]
        j = i - 1
        while (j >= 0 and len(coefficient) < 3
               and nonzero[j][0] == highest - len(coefficient)):
            coefficient.append(str(nonzero[j][1]))
            j -= 1
        lowest = nonzero[j + 1][0]
        pieces.append(''.join(coefficient) + ':' + str(lowest))
        i = j
    return ' '.join(pieces + ['0:0'])


def solve():
    first = input().strip()
    second = input().strip()
    print(add_sparse(first, second))


if __name__ == '__main__':
    solve()

复杂度分析

设 $L$ 为两个编码展开后的数字位总数。

时间复杂度:$O(L\log L)$,主要用于指数排序及堆处理;每个原始或进位产生的指数至多入堆、出堆一次。

空间复杂度:$O(L)$,字典、堆及排序后的非零数字位均与输入数字位数量成正比,不随最高指数大小增长。


第 3 题:黄金投资

题目描述

已知未来若干天的黄金价格。初始资金为 budget,最多进行 max_trades 笔交易,每次买入和卖出固定数量 investment_num 的黄金。必须先买入、再于之后某天卖出;任意时刻至多持有一笔交易。买入时账户资金必须足够支付全部买入成本,卖出时一次性卖出持有的黄金并支付一笔手续费 fee。卖出所得资金可以用于后续交易。

求最终能够获得的最大收益;如果不进行交易更好,收益为 $0$。

输入格式与约束

  • 第一行是天数 days,满足 $2\le days\le500$。
  • 第二行是 days 个价格 prices[i],满足 $1\le prices[i]\le1000$。
  • 接下来三行依次是手续费 fee、初始资金 budget、最多交易次数 max_trades,满足 $0<fee\le1000$、$0<budget\le10000$、$0<max_trades\le100$。
  • 最后一行是每笔交易固定买入的数量 investment_num,满足 $1\le investment_num\le30$。

输出格式

输出可获得的最大收益。如果没有可以获利的交易,输出 $0$。

样例 1

输入

4
3 5 2 7
1
10
1
2

输出

9

解释:第 3 天以 $2$ 的价格买入 $2$ 单位,花费 $4$;第 4 天以 $7$ 的价格卖出,收入 $14$,扣除手续费 $1$ 后净收益为 $14-4-1=9$。

样例 2

输入

6
1 3 2 8 4 9
2
10
2
1

输出

8

解释:先以价格 $1$ 买入、以 $8$ 卖出,扣费后的收益为 $5$;再以价格 $4$ 买入、以 $9$ 卖出,扣费后的收益为 $3$,总收益为 $8$。

思路分析

第一步:资金约束决定需要保留账户资金。

只记录累计收益不足以判断后续交易是否合法:即使某笔交易利润很高,买入下一笔时仍必须有足够资金支付固定数量黄金的成本。因此状态直接记录交易后的现金,而不是只记录收益。

第二步:分别维护空仓与持仓状态。

令 cash[j] 表示处理完当前天后,已完成 $j$ 笔交易且没有持仓时的最大现金;hold[j] 表示已经买入第 $j$ 笔、尚未卖出时手头剩余现金的最大值。初始化只有 cash[0] = budget 可达,其余状态均不可达。

若当天价格为 $p$,买入成本是 $p\times investment_num$。从前一天的 cash[j - 1] 买入第 $j$ 笔,前提是现金不少于成本;卖出第 $j$ 笔时,现金增加 $p\times investment_num-fee$。空仓或持仓不动则保留原状态。

第三步:用新数组隔离每天的转移。

每天从上一天的 cash、hold 计算新状态,再整体替换。这样同一天的买入结果不能立刻用于卖出,也不能用同一天卖出的现金再次买入。最终在所有空仓状态中取最大现金,并减去初始资金;不交易的状态也在其中,所以收益不会小于零。

正确性证明

初始时没有完成交易、没有持仓且现金为 budget,只有 cash[0] 可达,状态定义成立。

假设一天开始前的状态正确。结束时处于空仓的方案,要么延续此前空仓,要么卖出此前已经买入的第 $j$ 笔交易;对应转移分别保留 cash[j] 或将 hold[j] 加上卖出收入并扣除手续费。结束时持仓的方案,要么延续此前持仓,要么从已完成 $j-1$ 笔交易的空仓状态买入第 $j$ 笔;买入转移检查现金足够,因而不会产生资金不足的非法方案。逐日新数组保证买入与卖出发生在不同天。

每个状态保留同类方案中的最大现金是安全的:更高的现金不会减少任何未来买入的可行性,且所有后续收支只依赖当前状态与未来价格。归纳可知,处理完所有天后,空仓状态覆盖全部合法的已完成交易方案,并保留每种交易次数下的最大最终资金。取其最大值并与初始资金比较,即得最大非负收益。

题解代码

import sys
input = sys.stdin.readline


def max_profit(prices, fee, budget, max_trades, investment_num):
    unreachable = -10**30
    cash = [unreachable] * (max_trades + 1)
    hold = [unreachable] * (max_trades + 1)
    cash[0] = budget

    for price in prices:
        cost = price * investment_num
        next_cash = cash[:]
        next_hold = hold[:]
        for completed in range(max_trades + 1):
            if hold[completed] != unreachable:
                sale_cash = hold[completed] + cost - fee
                if sale_cash > next_cash[completed]:
                    next_cash[completed] = sale_cash
            if completed < max_trades and cash[completed] >= cost:
                remaining_cash = cash[completed] - cost
                if remaining_cash > next_hold[completed + 1]:
                    next_hold[completed + 1] = remaining_cash
        cash, hold = next_cash, next_hold

    return max(0, max(cash) - budget)


def solve():
    days = int(input())
    prices = list(map(int, input().split()))[:days]
    fee = int(input())
    budget = int(input())
    max_trades = int(input())
    investment_num = int(input())
    print(max_profit(prices, fee, budget, max_trades, investment_num))


if __name__ == '__main__':
    solve()

复杂度分析

时间复杂度:$O(days\cdot max_trades)$,每天对每个交易次数状态做常数次转移。

空间复杂度:$O(max_trades)$,滚动保存前后两天的空仓与持仓状态。


小结

  • 物流中转网络:以步数分层,沿边从旧状态转移到新状态,能够准确统计允许重复经过节点的定长路径。
  • 大数加法:只保存非零指数位,再从低位处理进位,空间与编码长度相关而非最高指数相关。
  • 黄金投资:资金可行性要纳入状态,空仓和持仓分开转移,逐日更新以保证买入和卖出发生在不同天。