大厂真题 / 网易互娱

网易互娱 4.12 笔试真题

本场考试概述

考试时间:2026年4月12日 考试岗位:通用岗 难度评级:困难

考点分析

  1. 第一题:网格模拟 + 光线追踪(难度中等)
  2. 第二题:拓扑排序 + DFS剪枝(难度困难)

建议策略

  1. 第一题逐灯模拟光线,用查表处理镜子反射,注意防止镜子构成环路导致死循环
  2. 第二题 N 小于等于 10,规模极小,DFS 枚举分配方案配合三重剪枝即可通过

第一题:照明

题目描述

给定一个 n 行 m 列的网格地图,每个格子是以下字符之一:

  • '#' 障碍物
  • '.' 空地
  • '/''\' 镜子
  • 'L''R''U''D' 灯,分别表示向左、右、上、下照射

灯光从灯所在格子出发,沿当前方向逐格传播。遇到障碍物或另一盏灯时,光线在进入前停止(障碍物和灯格子都不会被照亮,也不会被穿过)。遇到镜子时,光线改变方向继续传播:

  • '/' 镜子:L 变 D、R 变 U、U 变 R、D 变 L
  • '\' 镜子:L 变 U、R 变 D、U 变 L、D 变 R

镜子本身不计入光照强度,但光线可以穿过镜子继续传播。每个空地格子的光照强度等于能照到它的灯的数量(每盏灯最多贡献 1)。输出地图中每个格子的光照强度,非空地格子输出 -1。

样例

输入

3 4
R..#
.#..
..L.

输出

-1 1 1 -1
0 -1 0 0
1 1 -1 0

思路分析

第一步:理解问题本质

每盏灯发出一条光线,光线沿固定方向传播,碰到镜子会拐弯,碰到墙或其他灯会停止。我们要统计每个空地格子被多少条不同光线覆盖。题目给出灯数量和镜子数量都有上限,因此直接逐灯模拟每条光线的完整路径是可行的。

第二步:为什么选择逐灯模拟

每条光线的路径是唯一确定的——从灯出发,沿初始方向走,碰镜子拐弯,碰墙停下。镜子最多 100 个,所以一条光线最多拐 100 次弯,每段直线最长 max(n, m)。总计算量完全可控,不需要复杂的数据结构。

第三步:用查表处理镜子反射

将四个方向 L、R、U、D 编号为 0、1、2、3,用数组存储每个方向的行列增量。两种镜子的反射效果也用数组查表:'/' 镜子把方向 d 变成 3-d(即 L 和 D 互换、R 和 U 互换),'\' 镜子把方向 d 变成 d^1(二进制翻转最低位,即 L 和 U 互换、R 和 D 互换)。

第四步:处理镜子环路

如果若干面镜子恰好构成环路,光线会永远转圈。为此用一个集合记录”哪面镜子被从哪个方向进入过”,如果重复出现则说明在绕圈,立刻终止该光线。

第五步:以样例验证

灯 R 在 (0,0),向右照射:(0,1) 和 (0,2) 是空地被照到,(0,3) 是障碍物停止。灯 L 在 (2,2),向左照射:(2,1) 和 (2,0) 是空地被照到。最终 (0,1)、(0,2) 光照为 1,(2,0)、(2,1) 光照为 1,其余空地为 0,非空地输出 -1,与样例输出一致。

题解代码

import sys
input = sys.stdin.readline

def main():
    n, m = map(int, input().split())
    grid = [input().strip() for _ in range(n)]

    light = [[0] * m for _ in range(n)]

    # 方向编号: L=0, R=1, U=2, D=3
    DR = [0, 0, -1, 1]
    DC = [-1, 1, 0, 0]
    # '/' 反射查表: L->D, R->U, U->R, D->L
    REF_SLASH = [3, 2, 1, 0]
    # '\' 反射查表: L->U, R->D, U->L, D->R
    REF_BACK = [2, 3, 0, 1]

    DIR_MAP = {'L': 0, 'R': 1, 'U': 2, 'D': 3}
    LAMPS = set('LRUD')

    for r in range(n):
        for c in range(m):
            if grid[r][c] not in LAMPS:
                continue
            # 从灯出发追踪光线
            d = DIR_MAP[grid[r][c]]
            cr, cc = r, c
            visited = set()
            while True:
                nr, nc = cr + DR[d], cc + DC[d]
                if nr < 0 or nr >= n or nc < 0 or nc >= m:
                    break
                ch = grid[nr][nc]
                # 障碍物和其他灯会挡住光线
                if ch == '#' or ch in LAMPS:
                    break
                if ch == '.':
                    light[nr][nc] += 1
                    cr, cc = nr, nc
                else:
                    # 镜子:记录(位置,方向)防止无限循环
                    key = (nr, nc, d)
                    if key in visited:
                        break
                    visited.add(key)
                    d = REF_SLASH[d] if ch == '/' else REF_BACK[d]
                    cr, cc = nr, nc

    # 空地输出光照强度,非空地输出-1
    lines = []
    for r in range(n):
        row = []
        for c in range(m):
            row.append(str(light[r][c]) if grid[r][c] == '.' else '-1')
        lines.append(' '.join(row))
    print('\n'.join(lines))

main()

复杂度分析

时间复杂度:O(L * K * max(n, m)),其中 L 是灯数,K 是镜子数。每条光线最多拐 K 次弯,每段直线最长 max(n, m)。

空间复杂度:O(n * m),用于存储光照强度矩阵。


第二题:并行编译

题目描述

代码工程编译时需要编译所有 Target。Target 之间存在单向依赖关系,当前 Target 依赖的所有 Target 编译完成后才能开始编译,且每个线程上一次只能编译一个 Target,不能暂停或中断。

现在有 N 个 Target 和一个双线程 CPU,请安排编译调度方案使总耗时最短。如果存在循环依赖导致无法完成编译,则输出 -1。

样例

输入

3 2
4
3
2
1 2
2 3

输出

9

思路分析

第一步:理解问题本质

N 个任务有依赖关系(DAG),用 2 个线程并行执行,每个线程同一时刻只能做一个任务。要求找到一种调度方案使得所有任务完成的最早时刻最小。如果有循环依赖,则无解。

第二步:为什么选择 DFS 枚举

这是一个 NP-hard 问题(双机调度),没有多项式时间的最优算法。但题目给出 N 小于等于 10,总共只有 10 个任务。每个任务要么放线程 1 要么放线程 2,最多 2^10 = 1024 种分配方式。加上执行顺序受依赖约束,实际搜索空间更小。因此直接 DFS 回溯搜索所有合法调度方案是完全可行的。

第三步:环检测——拓扑排序

首先检测是否存在循环依赖。方法是经典拓扑排序:维护每个节点的入度,不断取出入度为 0 的节点放入队列,处理后将其后继的入度减 1。如果最终处理的节点数不等于 N,说明存在环,输出 -1。

第四步:DFS 搜索框架

用位掩码 mask 记录哪些任务已完成,同时维护两个线程的空闲时刻 t1 和 t2,以及每个任务的实际完成时间 finish[i]。每一步,从尚未完成且依赖已全部满足的任务中选一个,尝试分配给两个线程之一。任务 i 的最早开始时间 = max(线程空闲时刻, 所有依赖的完成时间)。

第五步:三重剪枝大幅减少搜索量

  1. 对称性剪枝:两个线程是等价的,互换不影响结果。每步保证 t1 <= t2,搜索量直接减半。
  2. 最优性剪枝:如果当前两个线程中较大的空闲时刻已经大于等于已知最优解,不可能更好,立刻回溯。
  3. 去重剪枝:如果任务 i 分配到两个线程的开始时间恰好相同(即 max(t1, ready) == max(t2, ready)),两个分支等价,只试一个即可。

第六步:以样例验证

3 个任务耗时 4、3、2,依赖链 1->2->3。必须先做 3(耗时 2),再做 2(耗时 3),最后做 1(耗时 4)。由于严格的依赖链,无法并行,总耗时 2+3+4=9。

题解代码

import sys
from collections import deque
input = sys.stdin.readline

def main():
    N, K = map(int, input().split())
    w = [int(input()) for _ in range(N)]

    # dep_mask[i]的第j位为1表示任务i依赖任务j
    dep_mask = [0] * N
    deps = [[] for _ in range(N)]
    children = [[] for _ in range(N)]
    in_deg = [0] * N

    for _ in range(K):
        u, v = map(int, input().split())
        u -= 1; v -= 1
        dep_mask[v] |= (1 << u)
        deps[v].append(u)
        children[u].append(v)
        in_deg[v] += 1

    # 拓扑排序检测环
    q = deque(i for i in range(N) if in_deg[i] == 0)
    cnt = 0
    temp = in_deg[:]
    while q:
        u = q.popleft()
        cnt += 1
        for v in children[u]:
            temp[v] -= 1
            if temp[v] == 0:
                q.append(v)

    if cnt != N:
        print(-1)
        return

    full = (1 << N) - 1
    best = sum(w)
    finish = [0] * N

    def dfs(mask, t1, t2):
        nonlocal best
        # 对称性剪枝:保证 t1 <= t2
        if t1 > t2:
            t1, t2 = t2, t1
        # 最优性剪枝
        if t2 >= best:
            return
        if mask == full:
            best = t2
            return
        for i in range(N):
            if mask >> i & 1:
                continue
            # 检查依赖是否全部满足
            if dep_mask[i] & mask != dep_mask[i]:
                continue
            ready = max((finish[j] for j in deps[i]), default=0)
            old = finish[i]
            # 尝试分配到线程1
            s1 = max(t1, ready)
            finish[i] = s1 + w[i]
            dfs(mask | (1 << i), s1 + w[i], t2)
            finish[i] = old
            # 尝试分配到线程2(去重剪枝:开始时间相同则跳过)
            s2 = max(t2, ready)
            if s2 != s1:
                finish[i] = s2 + w[i]
                dfs(mask | (1 << i), t1, s2 + w[i])
                finish[i] = old

    dfs(0, 0, 0)
    print(best)

main()

复杂度分析

时间复杂度:最坏 O(N! * 2^N),但三重剪枝使实际搜索量远小于此,N <= 10 下毫秒级完成。

空间复杂度:O(N),递归深度和辅助数组均为 O(N)。


小结

  • 第一题网格光线模拟,逐灯追踪光线路径,用查表处理两种镜子的反射,用集合检测镜子环路防止死循环
  • 第二题并行编译是经典双机调度问题,利用 N <= 10 的小规模特点,DFS 枚举所有合法调度方案,配合对称性、最优性、去重三重剪枝高效求解