大厂真题 / 华为
华为研发岗 2026-09-11
本场考试概述
考试时间:2026-09-11
考试岗位:研发岗
考点分析:三道编程题,依次是因数跳跃(质因数分解与 BFS)、流水线任务最优调度(单调队列优化 DP)、算法题目设计方案(组合回溯)。难度评估为中等(编辑判断)。
本页包含三题完整题意、数据范围、全部样例与可运行 Python 解法。
建议策略:调度题必须检查开头、中间、末尾的跳过长度;组合题按编号递增搜索,先保证总数完整,再截取输出,不能找到三个方案就结束搜索。
第 1 题:因数跳跃
题目描述
给定正整数数组 nums,从下标 0 出发。在下标 i 处,可以选择 nums[i] 的任一质因数 p,跳到 i+p 或 i-p;每个目标分别检查是否在 [0,n-1] 内,不能越界,不取模。判断能否恰好到达下标 n-1。
例如 12 的不同质因数为 2、3,而不是 4、6 或 12。元素为 1 时没有出边;数组只有一个元素时,起点已经是终点。
输入描述
一行非空数组,以空格分隔,不额外输入长度。$1\le n\le 10^4$,$1\le nums[i]\le 10^3$。
输出描述
能到达末尾输出小写 true,否则输出小写 false。
样例 1
输入
5 2 3 5 1 10 7
输出
false
起点只能跳至下标 5;之后只能回到下标 0 或到达下标 3,无法到达末尾。
样例 2
输入
12 2 4 1 1
输出
true
沿下标 0 → 2 → 4,每次使用质因数 2。
样例 3
输入
1 1
输出
false
思路分析
第一步:把移动转为有向边。 下标是节点,当前数的每个不同质因数产生至多两条出边。目标节点的值不一定包含相同质因数,因此不能按无向图使用并查集。
第二步:预处理最小质因子。 令 V 为数组最大值,筛出每个整数的最小质因子。分解时取出质因子 p 并除尽其所有幂,避免重复生成相同步长。1 不产生任何步长。
第三步:BFS 判可达。 起点入队并标记,每次枚举合法且未访问的目标。到达末尾立即返回,否则队列耗尽即不可达。在入队时标记,防止环路导致重复搜索。
正确性说明
质因子分解恰好枚举当前值的所有不同质因数,分别检查左右目标,故生成的边与合法移动完全相同。BFS 只沿合法边扩展,因此访问的节点都能到达;反之,对任意合法路径按长度归纳,其每个节点最终都会被搜索访问。因此末尾被访问当且仅当存在合法跳跃方案。
题解代码
import sys
from collections import deque
def can_reach(nums):
n = len(nums)
limit = max(nums)
smallest = [0] * (limit + 1)
for p in range(2, limit + 1):
if smallest[p] == 0:
for value in range(p, limit + 1, p):
if smallest[value] == 0:
smallest[value] = p
seen = [False] * n
seen[0] = True
queue = deque([0])
while queue:
i = queue.popleft()
if i == n - 1:
return True
value = nums[i]
while value > 1:
p = smallest[value]
while value % p == 0:
value //= p
for target in (i - p, i + p):
if 0 <= target < n and not seen[target]:
seen[target] = True
queue.append(target)
return False
def solve():
nums = list(map(int, sys.stdin.readline().split()))
print('true' if can_reach(nums) else 'false')
if __name__ == '__main__':
solve()
复杂度分析
时间复杂度:$O(V\log\log(V+2)+n\log(V+1))$,其中 V 为最大元素值。筛表后每个下标至多处理一次,每次分解至多进行对数次除法。
空间复杂度:$O(n+V)$,用于输入、访问数组、队列与最小质因子表。
易错点
- 左右目标分别判界,不要求两个方向同时合法。
- 1 不是质因数;重复质因数只需生成一次步长。
- 长度为 1 时即使元素为 1,也应输出
true。
第 2 题:流水线任务最优调度
题目描述
任务按数组顺序排列,每个元素是执行该任务的时间。每个任务可以执行或跳过,要求恰好执行 k 个任务,任意连续跳过的任务数不超过 max_skip,使执行时间总和最小。
开头没有执行的任务、两次执行之间的任务,以及完成最后一个任务后剩余的全部任务,都计入相应的连续跳过段。若无法完成要求,输出 -1。
输入描述
第一行是任务耗时数组,以空格分隔;空行表示空数组。第二行为 k,第三行为 max_skip。数据范围:$0\le n\le 1000$,$1\le cost[i]\le 10^4$,$0\le k\le n$,$0\le max_skip\le 1000$。
输出描述
一个整数,表示最小总耗时;不存在合法方案则为 -1。
样例 1
输入
1 2 3 4 5
3
0
输出
-1
不允许跳过任何任务,却只执行三个,因而无解。
样例 2
输入
3 1 4 2 5
3
2
输出
6
执行第 1、2、4 个任务,总耗时为 6,所有跳过段均不超限。
样例 3
输入
1 100 1 100 1
3
1
输出
3
执行第 1、3、5 个任务。
样例 4
输入
0
1
输出
0
首行为空,表示没有任务;后两行分别为 k=0 和 max_skip=1。复制输入时须保留第一行。
补充自测
输入
8 1 9 2 7
2
1
输出
3
执行编号 2、4(从 1 开始)两个任务,开头、中间、末尾各跳过一个,总耗时为 3。
思路分析
第一步:把全局约束改成位置间隔。 假设最后执行的任务下标为 i,上一次执行下标为 p,则两者之间跳过 i-p-1 个任务,所以 p 必须在 [i-max_skip-1, i-1] 内。
第二步:定义 DP。 f[j][i] 表示恰好执行 j 个任务,且第 j 个任务是下标 i 时的最小总耗时。第一层只有 i <= max_skip 的位置可达,值为 cost[i];后续层在上述窗口中取上一层最小值,再加 cost[i]。
第三步:使用单调队列。 对同一层,随着 i 向右移动,前驱窗口也单调移动。队列保存上一层下标,对应 DP 值递增;新值不大于队尾时淘汰队尾,下标过旧时淘汰队头。于是每层从枚举全部前驱变为 O(n) 扫描。只需保留前后两层,不需要 O(kn) 的整张表。
第四步:检查尾段和特殊情况。 答案只在 i >= n-1-max_skip 的最后位置中取最小值。k 为 0 时全部任务都是一个跳过段,只有 n <= max_skip 才合法。k 大于 n 时直接无解。
正确性说明
任一合法的 j 次执行方案删去最后一次执行,必落到允许前驱窗口内的某个 f[j-1][p]。反过来,将 i 接到窗口内的任一可达前驱后,中间跳过长度一定合法。转移因此既不漏解也不引入非法解。初始化约束开头跳过段,最终取值约束尾段,单调队列只优化求最小值而不改变转移。
题解代码
import sys
from collections import deque
def min_total_time(cost, k, max_skip):
n = len(cost)
if k == 0:
return 0 if n <= max_skip else -1
if k > n:
return -1
infinity = float('inf')
prev = [infinity] * n
for i in range(min(n, max_skip + 1)):
prev[i] = cost[i]
for _ in range(2, k + 1):
current = [infinity] * n
queue = deque()
for i in range(1, n):
while queue and prev[queue[-1]] >= prev[i - 1]:
queue.pop()
queue.append(i - 1)
while queue and queue[0] < i - max_skip - 1:
queue.popleft()
if queue and prev[queue[0]] != infinity:
current[i] = prev[queue[0]] + cost[i]
prev = current
answer = min(prev[max(0, n - 1 - max_skip):], default=infinity)
return -1 if answer == infinity else answer
def solve():
input = sys.stdin.readline
cost = list(map(int, input().split()))
k = int(input())
max_skip = int(input())
print(min_total_time(cost, k, max_skip))
if __name__ == '__main__':
solve()
复杂度分析
时间复杂度:常规 k ≥ 1 时 O(nk),每层各下标进出队列至多一次;包括 k 为 0 的输入读取时,总体上界可写 O(n(k+1))。
空间复杂度:O(n),输入数组、滚动 DP 数组及队列。
易错点
- 不能在完成第 k 个任务后忽略尾部,否则可能把不可行方案判成最优。
- 前驱最远可到
i-max_skip-1,不是i-max_skip。 - 空数组需要按行读取;全局
split()会抹去代表空数组的首行。
第 3 题:算法题目设计方案
题目描述
有 n 个知识点模块,编号从 1 到 n,每个模块有一个正整数难度系数。选择 k 个模块,并按编号递增排列:
- 相邻选中编号之差必须大于冲突阈值 d。
- 难度系数之和必须在闭区间 [low, high] 内。
统计所有合法方案,并按编号数值的字典序输出最小的至多三个方案。总数统计不能只统计打印的方案。
输入描述
第一行五个整数 n、k、d、low、high;第二行 n 个正整数难度系数。数据范围:$1\le n\le 20$,$1\le k\le n$,$0\le d\le n$,$1\le low\le high\le 1000$,每个难度系数在 [1,100] 内。
输出描述
第一行合法方案总数,随后输出按字典序排列的前 min(3, 总数) 个方案,一行一个,编号空格分隔。无方案时只输出 0。
样例 1
输入
5 3 1 10 20
3 5 2 4 6
输出
1
1 3 5
三个编号之间至少相差 2,仅有 1、3、5,难度和为 11。
样例 2
输入
4 2 0 5 15
2 3 4 5
输出
6
1 2
1 3
1 4
任意两个不同编号都不冲突,六种组合的难度和均在范围内。
样例 3
输入
6 3 1 10 30
5 5 5 5 5 5
输出
4
1 3 5
1 3 6
1 4 6
第四个方案为 2、4、6;仍须计入总数,但不打印。
思路分析
第一步:按递增编号构造。 搜索状态包括下一可选编号 start、当前路径和难度和 total。选择编号 i 后,下一个编号至少为 i+d+1,因此不需要额外检查已经满足的冲突条件。
第二步:让遍历顺序就是字典序。 每一层按编号从小到大选,两个完整方案首次不同的位置上,更小编号对应的搜索分支先访问。于是无需先存储并排序全部方案,只保存最早找到的三个,同时持续累加总数。
第三步:进行安全剪枝。 还需要选 remaining 个模块时,当前第一个编号 i 后必须放得下 remaining-1 个间距至少为 d+1 的编号,所以 i <= n-(remaining-1)*(d+1)。难度都为正数,选入当前模块就超过 high 时,可以跳过该模块,但不能结束整层循环,因为后续编号的难度并不保证更大。
正确性说明
每个合法方案只有一种递增编号序列,DFS 会沿唯一分支访问它。编号下界保证冲突约束,数量剪枝只丢弃空间不足的分支,正数和剪枝只丢弃不可能满足上界的分支;选满后检查下界,故计数恰好覆盖所有合法方案。递增枚举保证最先记录的三个方案是数值字典序最小的三个。
题解代码
import sys
def design_plans(difficulty, k, d, low, high):
n = len(difficulty)
count = 0
best = []
path = []
def dfs(start, total):
nonlocal count
remaining = k - len(path)
if remaining == 0:
if low <= total <= high:
count += 1
if len(best) < 3:
best.append(path.copy())
return
last = n - (remaining - 1) * (d + 1)
for i in range(start, last + 1):
new_total = total + difficulty[i - 1]
if new_total > high:
continue
path.append(i)
dfs(i + d + 1, new_total)
path.pop()
if 0 <= k <= n:
dfs(1, 0)
return count, best
def solve():
input = sys.stdin.readline
n, k, d, low, high = map(int, input().split())
difficulty = list(map(int, input().split()))
count, best = design_plans(difficulty, k, d, low, high)
print(count)
for plan in best:
print(*plan)
if __name__ == '__main__':
solve()
复杂度分析
时间复杂度:O(n + Σ C(n,t) + k),求和范围为 t=0 到 k。n 项为输入读取;不计剪枝时,每次尝试对应一条长度不超过 k 的递增序列,每个节点常数次运算,至多复制三个长度 k 的方案。它是指数级枚举上界,不能仅因保留三个方案就写成 O(k)。
空间复杂度:O(n+k),包括输入、递归路径与栈、至多三个输出方案。递归深度最多为 20,符合本题小规模回溯的限制。
易错点
- 难度上、下界均包含等号,编号差却必须严格大于 d。
- 必须复制路径,不能保存后续会被修改的同一个列表。
- 找到三个方案后仍需搜索,才能得到真实总数。
小结
三题分别练习有向图可达性、滑动窗口优化 DP 和按字典序回溯计数。先严格翻译跳跃方向、首尾间隔与冲突不等式,再考虑优化,可避免大部分边界错误。