蚂蚁2026春招笔试真题 - 算法岗(3.12)
AK机给大家整理了一份高质量的互联网大厂、大模型独角兽的内推和暑期实习/春秋招投递信息汇总,每日更新,建议收藏:
https://docs.qq.com/smartsheet/DUXJLSnZoTVFhVUVs?tab=sc_K49vrh
蚂蚁作为阿里系中独立笔试的公司,一直是算法岗同学的重点关注对象。AK机第一时间整理了这场蚂蚁26春招笔试的完整题解和代码,涵盖枚举、HMM Viterbi 和序列DP三个核心考点。
本场考试概述
考试时间:2026年3月12日
考试岗位:算法岗
难度评级:中等偏难
考点分析:
1. 第一题:枚举/模拟(难度 Easy-Medium) 2. 第二题:HMM Viterbi 动态规划 + NumPy 实现(难度 Medium) 3. 第三题:序列DP + 贪心选择(难度 Hard)
建议策略:
1. 第一题是分类讨论 + 枚举,想清楚三种情况即可,10-15分钟拿下 2. 第二题是经典 HMM Viterbi 算法的 NumPy 实现,熟悉概率图模型的同学可以快速搞定 3. 第三题是带约束的区间划分DP,需要预计算段贡献,难度较高但套路明确
备考推荐:蚂蚁属于阿里系公司,考点与阿里系笔试高度重合。推荐配合《阿里笔试速通指南》系统复习:第五章·动态规划(序列DP)对应第三题,第八章·AI/机器学习对应第二题的 NumPy 向量化实现。在线刷真题:https://www.neituiya.com/oj/7
第一题:增加or减少
题目描述
AK机有一个长度为 的数组 。AK机总共可以执行以下两种操作,其中第一种操作最多可执行一次,第二种操作也最多可执行一次(两种操作可以组合使用):
1. 选择两个不同的下标 ,将 修改为 ,花费 的代价。 2. 选择一个元素 以及任意正整数 ,对其增加或减少 ,花费 的代价。
AK机希望花费最小的代价,使得 。
请输出这个最小代价。
输入描述
每个测试文件均包含多组测试数据。
第一行输入一个整数 ,表示数据组数。
每组测试数据描述如下:
第一行输入两个整数 。
第二行输入 个整数 。
保证单个测试文件中所有 的和不超过 。
输出描述
对于每组测试数据,新起一行输出一个整数,表示最小花费代价。
样例1
输入
2
3 5
1 -2 3
4 10
0 0 0 -5输出
1
0样例解释
对于第一组测试数据只需要将 减一即可。
题解:模拟
题目问题拆解
给定长度为 的数组和操作代价 ,要求通过至多一次加法操作(代价 )和至多一次增减操作(代价为增减量)使乘积为 。,,允许 枚举。
核心观察:乘积为 等价于至少存在一个 。
算法实现
算法主策略:本题采用枚举所有操作组合取最优。
分三种情况讨论:
1. 如果数组中已有 ,答案直接为 。 2. 仅用操作二:选 最小的元素直接变为 ,代价 。 3. 两种操作组合:先用操作一将 变为(代价),再用操作二将变为(代价)。枚举所有取 的最小值。
最终答案 。
时空复杂度分析
时间复杂度:,枚举所有元素对。
空间复杂度:,存储数组。
Python
# 增加or减少 - 枚举
defsolve(a, k):
n = len(a)
if0in a:
return0
# 仅操作二:最小绝对值
ans = min(abs(x) for x in a)
# 操作一+操作二:枚举所有对
for i inrange(n):
for j inrange(i + 1, n):
ans = min(ans, k + abs(a[i] + a[j]))
return ans
T = int(input())
for _ inrange(T):
n, k = map(int, input().split())
a = list(map(int, input().split()))
print(solve(a, k))第二题:离散型隐马尔可夫模型预测
题目描述
请在仅使用 的前提下,实现离散型隐马尔可夫模型()的 动态规划。
给定:
初始分布 。
状态转移矩阵 。
观测概率矩阵 (列索引=观测符号)。
多条离散观测序列 ,符号取值 。
请为每条序列计算:
1. 最优隐藏状态序列 2. 该序列的对数概率
实现要求:对数域计算,避免下溢。所有乘法用 ,求和用或。只需前向传递回溯,无需或训练。所有浮点以计算,输出对数概率保留 位小数(四舍五入)。
输入描述
单行 JSON: 为长度的数组,为矩阵,为矩阵,为 条观测序列(符号为整数)。
,序列长度 。所有概率矩阵行各自已归一化,不必检验。
输出描述
仅一行 JSON,包含 (每条序列最优路径)和 (对应对数概率, 保留小数位)。次序须与输入 保持一致。
样例1
输入
{"pi":[0.6,0.4],"A":[[0.7,0.3],[0.4,0.6]],"B":[[0.5,0.4,0.1],[0.1,0.3,0.6]],"obs":[[0,0]]}输出
{"paths":[[0,0]],"logp":[-2.253795]}题解:Viterbi 动态规划
题目问题拆解
实现 的 解码算法:给定模型参数 和观测序列,找到概率最大的隐藏状态路径及其对数概率。
算法实现
算法主策略:本题采用Viterbi 动态规划,在对数域下进行前向递推和回溯。
维度变化:: ,(回溯指针): 。
步骤:
1. 初始化:。 2. 递推:对 ,计算 ,同时记录 。 3. 终止:最优路径概率 ,最终状态 。 4. 回溯:。
时空复杂度分析
时间复杂度:,对每条序列的每个时刻枚举所有状态转移。
空间复杂度:,存储 和回溯指针。
Python
# 离散型隐马尔可夫模型预测 - Viterbi动态规划
import json
import numpy as np
defviterbi(log_pi, log_A, log_B, obs_seq):
"""单条序列的Viterbi解码"""
T = len(obs_seq)
N = len(log_pi)
# delta[t][j] = 到时刻t状态j的最优路径对数概率
delta = np.full((T, N), -np.inf, dtype=np.float64)
psi = np.zeros((T, N), dtype=np.int64)
# 初始化:delta_1(i) = log(pi_i) + log(B_i(o_1))
delta[0] = log_pi + log_B[:, obs_seq[0]]
# 递推:delta_t(j) = max_i[delta_{t-1}(i) + log(A_{ij})] + log(B_j(o_t))
for t inrange(1, T):
# trans[i][j] = delta_{t-1}(i) + log(A_{ij})
trans = delta[t - 1, :, None] + log_A # (N, N)
psi[t] = np.argmax(trans, axis=0) # 每列取最大的行索引
delta[t] = np.max(trans, axis=0) + log_B[:, obs_seq[t]]
# 回溯:从最后时刻的最优状态开始
path = np.zeros(T, dtype=np.int64)
path[T - 1] = np.argmax(delta[T - 1])
logp = delta[T - 1, path[T - 1]]
for t inrange(T - 2, -1, -1):
path[t] = psi[t + 1, path[t + 1]]
return path.tolist(), round(float(logp), 6)
data = json.loads(input())
pi = np.array(data["pi"], dtype=np.float64)
A = np.array(data["A"], dtype=np.float64)
B = np.array(data["B"], dtype=np.float64)
obs_list = data["obs"]
# 转到对数域,避免下溢
log_pi = np.log(pi)
log_A = np.log(A)
log_B = np.log(B)
paths = []
logps = []
for obs_seq in obs_list:
obs_seq = [int(x) for x in obs_seq]
path, logp = viterbi(log_pi, log_A, log_B, obs_seq)
paths.append(path)
logps.append(logp)
print(json.dumps({"paths": paths, "logp": logps}))第三题:简单数组划分
题目描述
给定两个整数 和一个长度为的数组。你需要将数组划分为个连续子数组,覆盖全部元素且互不重叠,每段长度至少为。
我们定义一段数组的贡献为:从该段中删除任意 个数后,剩余元素贡献之和。设该段的原始长度为 (删除前的长度),其中每个元素 的贡献定义为:
请问:如何划分才能使得所有段的总贡献最大?输出该最大贡献值。
输入描述
第一行输入五个整数 ,其中 为数组长度, 为划分的段数, 为每段需要删除的元素个数, 为题面参数。
第二行输入 个整数 ,表示数组元素。
保证 。
输出描述
输出一个整数,表示最大的总贡献值。
样例1
输入
5 2 1 0 1
1 3 2 4 5输出
800样例解释
在这个样例中,最优划分为 与。在第一段删除元素,该段无剩余元素,贡献为。在第二段删除最小元素,剩余元素,段长度为,贡献为。
总贡献为 。
样例2
输入
3 3 1 0 1
10 20 30输出
0样例解释
当每段长度为 且需要删除 个元素时,各段无剩余元素,因此总贡献为 。
题解:序列DP
题目问题拆解
将长度 的数组划分为 段(每段 ),每段删除 个元素后计算贡献,求最大总贡献。,允许 动态规划。
核心观察:每段的贡献与段长 、参数、以及保留哪些元素有关。当时删除最小的个以最大化贡献,当时删除最大的 个。
算法实现
采用动态规划。
状态方程定义
设 表示将前 个元素划分为 段的最大总贡献。
状态方程初始化
( 个元素划分 段,贡献为 )。其余 。
状态方程转移
枚举第 段的起点 ,终点 ,段长 :
其中 表示区间删除个最优元素后的贡献:将区间内排序,若删除最小的个,否则删除最大的个,贡献。
最终答案为 。
时空复杂度分析
时间复杂度:,DP 转移 ,预计算段贡献 。
空间复杂度:,DP 数组 ,段贡献 。
Python
# 简单数组划分 - 动态规划
defsolve():
n, m, k, b, c = map(int, input().split())
a = list(map(int, input().split()))
# 预计算 (a_i - b)^2,0-indexed
w = [(x - b) ** 2for x in a]
# 预计算 seg[l][r](0-indexed)
seg = [[0] * n for _ inrange(n)]
for l inrange(n):
vals = []
total = 0
for r inrange(l, n):
vals.append(w[r])
total += w[r]
length = r - l + 1
if length < k:
continue
sorted_vals = sorted(vals)
if c >= 0:
# 删除k个最小的
remove_sum = sum(sorted_vals[:k])
else:
# 删除k个最大的
remove_sum = sum(sorted_vals[-k:])
seg[l][r] = c * length * length * (total - remove_sum)
# DP: f[j][i] = 前i个元素划分为j段的最大贡献(0-indexed: 前i个 = a[0..i-1])
NEG_INF = float('-inf')
f = [[NEG_INF] * (n + 1) for _ inrange(m + 1)]
f[0][0] = 0
for j inrange(1, m + 1):
for i inrange(j * k, n + 1):
# 第j段为 [p, i-1](0-indexed),长度 i-p >= k
for p inrange((j - 1) * k, i - k + 1):
if f[j - 1][p] == NEG_INF:
continue
f[j][i] = max(f[j][i], f[j - 1][p] + seg[p][i - 1])
print(f[m][n])
solve()想要获取本场笔试代码,以及沟通交流求职等内容,可以加入下方AK机的校招交流群
