大厂真题 / 拼多多

拼多多 7.2 笔试真题 - 大模型算法岗

本场考试概述

考试时间:2026年7月2日 考试岗位:大模型算法岗 难度评级:中等

考点分析

  1. 第一题:对角线遍历矩阵——矩阵模拟(难度简单)
  2. 第二题:多多的营救行动——图论建模 + 拓扑剥离(难度中等)
  3. 第三题:多多捕蝇——函数图找环(难度中等)
  4. 第四题:CLIP 对比学习损失——NumPy 矩阵运算(难度中等)

建议策略

  1. 第一题是纯送分题,抓住”同一条对角线上行列差 $i-j$ 相同、相邻对角线方向交替”即可
  2. 第二、三题本质是同一套思想——在图上反复剥离”入度为 0”的节点,先稳拿第一题再攻这两道
  3. 第四题考的是徒手用 NumPy 复现 softmax 交叉熵,代码量不大,注意数值稳定(减去行最大值)和双向取平均

第 1 题:对角线遍历矩阵

题目描述

给定一个 $m \times n$ 的矩阵,按对角线遍历的顺序输出所有元素。从左下角出发,逐条对角线向右上推进,相邻对角线方向交替(第一条从下往上,第二条从上往下,依此类推)。

样例

输入

3 3
1 2 3
4 5 6
7 8 9

输出

7 4 8 9 5 1 2 6 3

思路分析

第一步:用行列差给对角线编号

观察发现同一条对角线上所有格子的行列差 $i - j$ 相同。从最左下那条对角线($i-j = m-1$)开始,到最右上($i-j = -(n-1)$)结束,共 $m+n-1$ 条。

第二步:确定每条对角线上的格子范围

固定差值 $d = i - j$ 后,行号 $i$ 要同时满足 $0 \leq i \leq m-1$ 和 $0 \leq i-d \leq n-1$,合并得到 $\max(0, d) \leq i \leq \min(m-1, n-1+d)$。

第三步:方向交替

用编号 $g$ 从 0 开始递增,$g$ 为偶数时从下往上取(行号递减),奇数时从上往下取(行号递增)。

题解代码

import sys
input = sys.stdin.readline

def solve():
    m, n = map(int, input().split())
    mat = [list(map(int, input().split())) for _ in range(m)]

    res = []
    for g in range(m + n - 1):
        d = (m - 1) - g
        lo = max(0, d)
        hi = min(m - 1, n - 1 + d)
        order = range(hi, lo - 1, -1) if g % 2 == 0 else range(lo, hi + 1)
        for i in order:
            res.append(mat[i][i - d])

    print(" ".join(map(str, res)))

solve()

复杂度分析

时间复杂度:$O(mn)$,每个格子恰好访问一次。 空间复杂度:$O(mn)$,存储矩阵。


第 2 题:多多的营救行动

题目描述

地牢中有 $n$ 个守卫,每个守卫站在自己的区域并监视若干其他区域。多多可以单杀或双杀守卫:

  • 单杀:守卫所在区域没有被其他存活守卫监视
  • 双杀:两个守卫彼此且仅彼此监视(各自只被对方监视),可同时打倒

被打倒的守卫不再具备监视能力。问能否打倒所有守卫?若不能,输出最少剩余数量。

样例

输入

2
1 1 2
2 0

输出

SUCCESS

思路分析

第一步:建成有向图

把”守卫 $j$ 监视区域 $r$”看作一条有向边 $j \to r$。用 monitored_by[r] 记录当前存活守卫中监视区域 $r$ 的集合。

第二步:两种安全操作对应两种结构

  • 单杀:区域 $i$ 的 monitored_by[i] 为空(入度为 0),可以安全打倒守卫 $i$
  • 双杀:守卫 $i$ 只被 $j$ 监视,且 $j$ 也只被 $i$ 监视,互为唯一监视者

第三步:剥离到不动点

反复执行:先尽量单杀所有入度为 0 的守卫,没有可单杀的再找一对可双杀的。每次打倒守卫只会让其他守卫的条件更容易满足(从别人的 monitored_by 中移除),不会制造新阻碍,因此贪心到底最优。

第四步:剩余守卫

剥离停止时若还有存活守卫,它们一定卡在相互纠缠的结构里(如三元环),无法安全消除。

题解代码

import sys
input = sys.stdin.readline

def solve():
    n = int(input())
    watch = [[] for _ in range(n + 1)]
    for _ in range(n):
        parts = list(map(int, input().split()))
        x, m = parts[0], parts[1]
        watch[x] = parts[2:2 + m]

    monitored_by = [set() for _ in range(n + 1)]
    for j in range(1, n + 1):
        for r in watch[j]:
            if 1 <= r <= n and r != j:
                monitored_by[r].add(j)

    alive = [i > 0 for i in range(n + 1)]
    cnt = n

    def kill(i):
        nonlocal cnt
        alive[i] = False
        cnt -= 1
        for r in watch[i]:
            if 1 <= r <= n:
                monitored_by[r].discard(i)

    changed = True
    while changed:
        changed = False
        for i in range(1, n + 1):
            if alive[i] and not monitored_by[i]:
                kill(i)
                changed = True
        if changed:
            continue
        for i in range(1, n + 1):
            if alive[i] and len(monitored_by[i]) == 1:
                j = next(iter(monitored_by[i]))
                if alive[j] and monitored_by[j] == {i}:
                    kill(i)
                    kill(j)
                    changed = True
                    break

    print("SUCCESS" if cnt == 0 else cnt)

solve()

复杂度分析

时间复杂度:$O(n \cdot E)$,$E$ 为监视关系总数,每轮剥离至少消除一个守卫。 空间复杂度:$O(n + E)$。


第 3 题:多多捕蝇

题目描述

$n$ 个房间,房间 $i$ 有出边指向 $a_i$(每个房间恰好一条出边)。苍蝇从任意房间出发沿边行走。在房间 $i$ 放粘蝇板的代价为 $c_i$。求最少总代价使得无论苍蝇初始在哪都能被捕获。

样例

输入

4
1 10 2 10
2 4 2 2

输出

10

思路分析

第一步:函数图的结构

每个节点出度恰好为 1,构成函数图。从任意节点出发一直走,房间数有限必然进入环。整张图由若干连通块组成,每个连通块是一个环加上汇入环的若干树枝。

第二步:为什么每个环放一块板就够

苍蝇最终会落入所在连通块的环里无限绕圈,只要环上有一块板必然经过;树枝上的房间早晚走进环,无需单独放板。不同环互不连通,必须各放至少一块。

第三步:取每个环上最便宜的房间

每个环选代价最小的房间放板,总答案是所有环最小 $c$ 之和。

第四步:用拓扑剥离找环

统计入度,把入度为 0 的房间不断出队删除(它们在树枝末端),删除时将其指向的房间入度减一。剥离结束后仍存活的房间恰好是所有环上的节点。最后沿出边遍历每个环累加最小值。

题解代码

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

def solve():
    n = int(input())
    c = [0] + list(map(int, input().split()))
    a = [0] + list(map(int, input().split()))

    indeg = [0] * (n + 1)
    for i in range(1, n + 1):
        indeg[a[i]] += 1

    # 拓扑剥离:入度为 0 的是树枝末端
    removed = [False] * (n + 1)
    q = deque(i for i in range(1, n + 1) if indeg[i] == 0)
    while q:
        u = q.popleft()
        removed[u] = True
        v = a[u]
        indeg[v] -= 1
        if indeg[v] == 0 and not removed[v]:
            q.append(v)

    # 遍历每个环取最小代价
    visited = [False] * (n + 1)
    ans = 0
    for i in range(1, n + 1):
        if not removed[i] and not visited[i]:
            mn = c[i]
            visited[i] = True
            x = a[i]
            while x != i:
                visited[x] = True
                if c[x] < mn:
                    mn = c[x]
                x = a[x]
            ans += mn

    print(ans)

solve()

复杂度分析

时间复杂度:$O(n)$,每个房间入队出队各一次,每个环完整遍历一次。 空间复杂度:$O(n)$。


第 4 题:CLIP 对比学习损失

题目描述

实现 CLIP 对比学习损失函数。给定已 L2 归一化的图像嵌入矩阵($N \times D$)、文本嵌入矩阵($N \times D$)和温度参数 $\tau$:

  1. 计算相似度矩阵 $S = I \cdot T^\top / \tau$
  2. 标签在对角线:第 $i$ 个图像匹配第 $i$ 个文本
  3. 图像→文本方向和文本→图像方向各做 softmax 交叉熵,取平均

输出最终损失值,保留 6 位小数。仅使用 NumPy。

样例

输入

{"image_embeds": [[1, 0], [0, 1]], "text_embeds": [[1, 0], [0, 1]], "temperature": 1.0}

输出

0.313262

思路分析

第一步:相似度矩阵

图像与文本先做矩阵乘法再除以温度,得到 $N \times N$ 的 logits 矩阵。第 $i$ 行第 $j$ 列是图像 $i$ 与文本 $j$ 的相似度。

第二步:对比学习的标签在对角线

一个 batch 里第 $i$ 个图像的正确匹配就是第 $i$ 个文本,标签为 $\text{diag}(0, 1, …, N-1)$。

第三步:数值稳定的交叉熵

对每一行先减去行最大值再取指数(避免溢出)。单行交叉熵 = log-sum-exp - 标签位置的分数(即对角元)。对所有行求平均得该方向的损失。

第四步:双向取平均

图像→文本对 $S$ 的每一行做 softmax 交叉熵;文本→图像对 $S^\top$ 做同样计算。最终损失为两者均值。

题解代码

import sys
import json
import numpy as np

def solve():
    data = json.loads(input())
    img = np.array(data['image_embeds'], dtype=np.float64)
    txt = np.array(data['text_embeds'], dtype=np.float64)
    temperature = float(data['temperature'])

    logits = img @ txt.T / temperature

    def cross_entropy(mat):
        row_max = mat.max(axis=1, keepdims=True)
        log_sum = row_max[:, 0] + np.log(np.exp(mat - row_max).sum(axis=1))
        diag = np.diag(mat)
        return float((log_sum - diag).mean())

    loss = (cross_entropy(logits) + cross_entropy(logits.T)) / 2.0
    print(f"{loss:.6f}")

solve()

复杂度分析

时间复杂度:$O(N^2 D)$,主要开销是相似度矩阵的矩阵乘法。 空间复杂度:$O(N^2)$,存储 logits 矩阵。


小结

  • 第一题是纯模拟题,按行列差分组对角线、方向交替遍历即可
  • 第二题建有向图后,反复剥离入度为 0 的节点和互为唯一监视者的节点对,贪心到不动点
  • 第三题利用函数图”每个连通块有且仅有一个环”的性质,拓扑剥离找环后取每环最小代价
  • 第四题是 CLIP 损失的标准实现,注意数值稳定(减行最大值)和双向对称