大厂真题 / 百度

百度 2026-8-20 笔试真题 - 算法岗

本场考试概述

考试时间:2026年8月20日

考试岗位:算法岗

难度评级:中等偏难

考点分析

  • 第一题:机器学习、深度学习、概率论和基础数据结构选择题
  • 第二题:相邻逆序交换模拟与逆序对上界剪枝
  • 第三题:对称配对差值求和与奇偶性判断
  • 第四题:排序、前缀和与分层贪心

建议策略

  • 第一题的操作次数可能达到 $10^9$,不能直接逐轮模拟;
  • 大输入量要使用快速读入;
  • 答案可能超过 32 位整数,使用 64 位整数或 Python 整数。

第 1 题:基础知识单选题

本场第一部分为基础知识单选题,覆盖机器学习、深度学习、概率论、分词器和数据结构等方向。下面列出代表性题目与考点。

1. 召回率

某二分类模型共有 40 个真实正类样本,其中检出 30 个。按

\[Recall = \frac{TP}{TP+FN}\]

计算,召回率为多少?

答案:$30/40=0.75$。

考点:召回率的分母是真实正类总数;查准率的分母才是预测为正的样本数。

2. SFT 数据中的编造事实

如果 SFT 数据中的 assistant 回复包含用户资料之外的编造事实,最直接的训练风险是:模型会把无依据补全当作监督信号,学习成一种回答模式。

考点:监督微调会逐 token 学习标注答案,数据中的错误示范不会因为进入训练集就自动被模型识别为错误。

3. 类别不平衡下的异常检测

正例占比很低时,模型全部预测为正常也可能取得很高 Accuracy。更合理的补充指标包括查准率、召回率、F1 和 PR-AUC。

考点:类别不平衡时不能只看 Accuracy;PR-AUC 对稀少正例通常更有诊断价值。

4. 卷积层参数量

输入通道数为 3,输出通道数为 16,卷积核大小为 $3\times3$,每个输出通道有一个 bias。参数量为:

\[3\times16\times3\times3+16=448\]

5. 方差的线性变换

若随机变量 $X$ 满足给定方差,令 $Y=aX+b$,则:

\[Var(Y)=a^2Var(X)\]

常数项不影响方差,系数需要平方。

6. 条件概率

如果事件 $A$ 发生 30 次,其中 $A$ 与 $B$ 同时发生 12 次,则按频率估计:

\[P(B\mid A)=\frac{12}{30}=0.4\]

7. 其他高频基础点

  • sigmoid 将实数映射到 $(0,1)$,可解释为二分类概率;
  • BPE、SentencePiece 等子词分词方法将文本切成 token,并在词表规模与未登录词之间做折中;
  • 栈遵循后进先出;
  • 选择排序第一趟会把未排序区间的最小值交换到首位。

第 2 题:相邻逆序交换洗牌

题目描述

给定一个长度为 $n$ 的排列 $p$,执行恰好 $k$ 次局部操作。每次从左到右找到第一个满足

\[p_i > p_{i+1}\]

的位置,交换 $p_i$ 与 $p_{i+1}$。如果当前排列不存在这样的相邻逆序,则本次操作不改变排列。

输出所有操作结束后的排列。

输入输出

每组数据给出 $n,k$ 和一个长度为 $n$ 的排列。输出最终排列。数据范围允许 $k$ 很大,因此不能简单执行 $k$ 轮完整扫描。

核心思路

找最左侧相邻逆序并交换,本质上是冒泡排序的一步。每交换一次相邻逆序对,排列的逆序对数量恰好减少 1;当排列变成升序后,后续操作全部空转。

因此,真正需要执行的交换次数最多是初始逆序对数量,而不是 $k$。实现时可以维护扫描指针:

  • 继续从上次位置寻找下降点,避免每次从头扫描;
  • 交换后指针最多向左回退一格,因为交换到左侧的是较小值;
  • 交换次数达到 $k$,或扫描到末尾时停止。

Python 参考实现

import sys


def shuffle_times(p, k):
    n = len(p)
    i = 0
    done = 0

    while done < k:
        while i + 1 < n and p[i] <= p[i + 1]:
            i += 1
        if i + 1 >= n:
            break
        p[i], p[i + 1] = p[i + 1], p[i]
        done += 1
        if i > 0:
            i -= 1
    return p


def solve():
    data = list(map(int, sys.stdin.buffer.read().split()))
    it = iter(data)
    t = next(it)
    ans = []
    for _ in range(t):
        n = next(it)
        k = next(it)
        p = [next(it) for _ in range(n)]
        ans.append(" ".join(map(str, shuffle_times(p, k))))
    print("\n".join(ans))


if __name__ == "__main__":
    solve()

复杂度

若实际发生了 $s$ 次交换,扫描指针的均摊移动量为 $O(n+s)$,空间复杂度为 $O(n)$。由于 $s$ 不超过初始逆序对数量,$k$ 很大时也不会真的执行 $k$ 轮空操作。


第 3 题:蜂蜜回文数组最少搬运

题目描述

给定长度为 $n$ 的正整数数组 $a$。一次操作可以从某个位置取出 1 单位,放入另一个不同位置。求最少操作次数,使数组变成回文数组;如果无法做到,输出 $-1$。

关键观察

对称位置 $(i,n-1-i)$ 最终必须变成同一个值。设这两个值为 $x,y$,把它们调整到同一个目标值 $t$ 所需的搬运总量为:

\[|x-t|+|y-t|\]
当 $t$ 位于 $x$ 和 $y$ 之间时,最小值为 $ x-y $。因此先计算所有对称位置的差值:
\[D=\sum_{i=0}^{\lfloor n/2\rfloor-1}|a_i-a_{n-1-i}|\]

每次搬运同时产生一次减少和一次增加,所以还要根据数组长度和总和的奇偶性修正。

奇偶性判断

  • 偶数长度:所有元素都属于某个对称 pair。回文数组总和必须为偶数,因此原数组总和为奇数时无解;否则答案为 $D/2$。
  • 奇数长度:中间元素可以承接剩余总量,始终有解;答案为 $\lceil D/2\rceil$。

Python 参考实现

import sys


def min_moves(a):
    n = len(a)
    d = sum(abs(a[i] - a[n - 1 - i]) for i in range(n // 2))

    if n % 2 == 0:
        if sum(a) % 2:
            return -1
        return d // 2
    return (d + 1) // 2


def solve():
    data = list(map(int, sys.stdin.buffer.read().split()))
    it = iter(data)
    t = next(it)
    ans = []
    for _ in range(t):
        n = next(it)
        a = [next(it) for _ in range(n)]
        ans.append(str(min_moves(a)))
    print("\n".join(ans))


if __name__ == "__main__":
    solve()

复杂度

每组只需扫描数组并计算对称差值,时间复杂度为 $O(n)$,额外空间复杂度为 $O(1)$(不计输入存储)。


第 4 题:连通图边权和最大化

题目描述

给定权值数组 $a$,构造整数数组 $b$。对任意两个不同节点 $i,j$:

  • 若 $b_i\ne b_j$,就在两点之间连一条边,边权为 $\min(a_i,a_j)$;
  • 若 $b_i=b_j$,两点之间不连边。

要求构造出的图连通,并最大化所有边权之和。

分层化简

$b$ 的具体数值并不重要,重要的是它把节点分成了若干层。不同层之间全部有边,同层之间没有边。为了保证连通,至少需要两层($n=1$ 时答案为 0)。

将 $a$ 按降序排列。若一个点位于某层,而它下面有若干点,则它会作为较小端点参与与这些点的连边,贡献为这些下方点的权值之和。于是应优先让较大的权值处在更低的层,尽可能被更多边计入。

定义降序数组的前缀和:

\[P_t=\sum_{i=0}^{t-1}a_i\]

枚举分层的切分位置,使用前缀和计算每种层数的贡献。由于 $a$ 已降序,$P_t$ 的增量单调不增,前缀和序列先增后降,可以线性扫描峰值和最优切分点。

参考实现

import sys


def best_sum(a):
    n = len(a)
    if n <= 1:
        return 0

    a.sort(reverse=True)
    pref = [0] * n
    for t in range(1, n):
        pref[t] = pref[t - 1] + a[t - 1]

    peak = 1
    for t in range(2, n):
        if pref[t] > pref[peak]:
            peak = t

    # 首层切在峰值之前的情况
    sum_pref = 0
    min_sum_pref = 0
    for k in range(1, peak):
        sum_pref += pref[k]
        min_sum_pref = min(min_sum_pref, sum_pref)
    sum_pref += pref[peak]
    best = (sum_pref - min_sum_pref) + (n - 1 - peak) * pref[peak]

    # 首层越过峰值后的情况
    for j in range(peak + 1, n):
        best = max(best, (n - j) * pref[j])
    return best


def solve():
    data = list(map(int, sys.stdin.buffer.read().split()))
    it = iter(data)
    t = next(it)
    ans = []
    for _ in range(t):
        n = next(it)
        a = [next(it) for _ in range(n)]
        ans.append(str(best_sum(a)))
    print("\n".join(ans))


if __name__ == "__main__":
    solve()

复杂度

排序是主要开销,时间复杂度为 $O(n\log n)$,前缀和与最优切分扫描为 $O(n)$,额外空间复杂度为 $O(n)$。


复盘建议

这场题目覆盖了算法基础、机器学习常识和构造/贪心问题。值得重点复习:

  1. 看到操作次数很大时,先找“真正发生变化的次数”的上界;
  2. 对称位置问题优先尝试配对差值和总量守恒;
  3. 构造题先忽略具体数值,寻找等价的分组或分层结构;
  4. ACM 模式下统一使用快速输入,并提前检查 64 位整数溢出;
  5. 先做能证明复杂度的解法,再考虑微优化。