2026-08-30 修订:根据评论区反馈,更正了 Q2 的样例和时间复杂度,以及 Q3 的 和平移方向;同时删除了无法核实的精确数据范围。
时间:2025年9月12日-9月13日
形式和考察范围等和夏令营基本一致
笔试
考察了:
-
命题逻辑、一阶逻辑
-
已知p1, p2, p3 = softmax(z1, z2, z3),真实标签为y1, y2, y3,求 softmax 关于输入求导,并结合交叉熵损失
设:
-
模型输出(logits)为:
-
经过 Softmax 后的预测概率为:
-
真实标签为 one-hot 编码形式:
-
交叉熵损失函数为:
我们的目标是求:损失 对 logits 的导数 ,这在反向传播中非常重要。
先计算 。
情况 1:当
情况 2:当
所以统一写成:
其中 是 Kronecker delta 函数( 时为 1,否则为 0)。
代入上面的导数表达式:
拆开求和:
由于 (one-hot 标签),所以:
总结
令:
- 为 one-hot 真实标签
则:
这个结果意味着,在实现分类网络时,Softmax + CrossEntropyLoss 的反向传播梯度就是预测概率与真实标签之差。
此外,现代框架(如 PyTorch)提供
LogSoftmax和NLLLoss或直接CrossEntropyLoss,内部已融合优化,避免显式计算 带来的数值问题。 -
-
动态规划:nxm 矩阵,左下角到右上角,每一步可以→↑↗,求方法总数
-
判断完全二叉树
-
关系分类(关系是不是反射的?)、判断映射(满射?)
-
贝叶斯定理
-
线性回归 wx+b
-
CNN 参数量计算
机试
前两题为强制使用python的算法题,后两题为强制使用C++的算法题。
注:已记不清当时的评测平台,也无法确认是否需要自行编写输入输出和头文件,请不要据此判断是 LeetCode 模式还是 ACM 模式。
Q1
略,较为简单
Q2 最长“山形”子序列
题目描述
给定一个长度为 的整数数组 ,求最长的子序列长度,该子序列满足:先严格递增,后严格递减。
按当时回忆,以下情况也被视为合法的“山形”子序列:
- 仅包含严格递增部分(没有下降段)
- 仅包含严格递减部分(没有上升段)
- 仅包含一个元素
子序列不要求连续。
输入格式
第一行一个正整数 ,表示数组长度。
第二行 个整数 ,表示数组元素。
输出格式
输出一个整数,表示满足条件的最长子序列的长度。
样例
样例输入 1
71 3 2 5 7 6 4样例输出 1
6样例解释 1
一个最长的合法子序列为 1 3 5 7 6 4,长度为 6。也可以选择 1 2 5 7 6 4。
样例输入 2
55 4 3 2 1样例输出 2
5样例解释 2 整个序列严格递减,是合法的(只有下降部分)。
样例输入 3
51 2 3 4 5样例输出 3
5样例解释 3 整个序列严格递增,是合法的(只有上升部分)。
原题的精确数据范围已记不清。本节的样例、输入输出描述与代码是根据题意重建的,不是官方原文。
提示 子序列可以从原序列中不连续地选取元素,但必须保持原有顺序。
解题思路
我们可以使用动态规划,并用“耐心排序”的方法在 时间内求出每个位置对应的长度:
- 计算以每个位置
i结尾的最长严格递增子序列长度dp_inc[i] - 计算以每个位置
i开始的最长严格递减子序列长度dp_dec[i] - 对于每个位置
i,它可以作为“山顶”,则通过它的最长山形子序列长度为:dp_inc[i] + dp_dec[i] - 1(减1是因为山顶被计算了两次) - 答案就是所有位置中的最大值
from bisect import bisect_left
def solve(): n = int(input()) a = list(map(int, input().split()))
# dp_inc[i]: 以位置 i 结尾的最长严格递增子序列长度 dp_inc = [0] * n tails = [] for i, value in enumerate(a): pos = bisect_left(tails, value) if pos == len(tails): tails.append(value) else: tails[pos] = value dp_inc[i] = pos + 1
# dp_dec[i]: 从位置 i 开始的最长严格递减子序列长度 dp_dec = [0] * n tails.clear() for i in range(n - 1, -1, -1): pos = bisect_left(tails, a[i]) if pos == len(tails): tails.append(a[i]) else: tails[pos] = a[i] dp_dec[i] = pos + 1
return max(dp_inc[i] + dp_dec[i] - 1 for i in range(n))
# 读取输入并输出结果print(solve())Q3 字符串平移拼接序列的第 K 个字符
题目描述
定义一个字符串生成序列 ,规则如下:
- 初始值:
- 递推规则:对于 ,有
- 其中 表示将字符串 中每个字符按字母表向后平移一位:
- 即循环右移:字符 变为
- 其中 表示将字符串 中每个字符按字母表向后平移一位:
例如:
给定一个正整数 ,请求出在某个足够大的 下, 的第 个字符(1-indexed)。
注意:由于 的长度随 指数增长,你不需要构造整个字符串,而应设计高效算法直接定位第 个字符。
输入格式
一行一个正整数 ,表示查询位置(从 1 开始计数)。
输出格式
输出一个字符,表示序列中第 个位置上的小写字母。
样例
样例输入 1
1样例输出 1
a样例输入 2
3样例输出 2
b样例解释 2 ,第 3 个字符是 。
样例输入 3
7样例输出 3
c样例解释 3 ,第 7 个字符是 。
原题的精确数据范围已记不清。本节的样例、输入输出描述与代码是根据题意重建的,不是官方原文。
提示
- 字符串长度满足:,
- 即
- 可通过递归方式求解:若 ,则答案在左半部分;否则在右半部分(需还原平移操作)。
解题思路
我们观察到:
- , 长度为
- ,长度为
关键性质:
- 每次操作将字符串长度翻倍
- 前半部分就是
- 后半部分是前半部分整体字符向后平移一位(z→a)
因此我们可以递归求解第 个字符:
- 找到最小的 使得 ,即确定 所在的字符串层级
- 如果 且当前视为 ,返回
'a' - 若 ,说明在左半部分,递归处理
- 若 ,说明在右半部分,对应位置为 ,递归得到该位置在左半部分的原始字符,再将其按字母表向后平移一位
注意:右半部分是左半部分的 shift 结果,所以找到对应位置的字符后要执行一次同向平移,并在 z 后回到 a。
#include <iostream>using namespace std;
char solve(long long k) { if (k == 1) { return 'a'; }
// 找到最小的 n 使得 2^n >= k long long length = 1; while (length < k) { length <<= 1; // 相当于 length *= 2 }
long long half = length >> 1; // 左半部分长度 = length / 2
if (k <= half) { // 在左半部分,递归处理 return solve(k); } else { // 在右半部分,对应左半部分的位置是 k - half char c = solve(k - half); // 右半部分字符 = 左半部分字符 + 1 (循环) return 'a' + (c - 'a' + 1) % 26; }}
int main() { long long k; cin >> k; cout << solve(k) << endl; return 0;}复杂度分析
- 时间复杂度:,每次递归将问题规模减半
- 空间复杂度:,递归栈深度
在 不超过 C++ long long 表示范围时,该实现可在 时间内完成计算。
Q4
略,较为简单
面试
自我介绍 英文:未来研究计划 项目 为何项目和科研兴趣不一致 机器学习 决策树 对三维重建有什么了解 自己的优点和缺点 transformer