蚂蚁2026暑期实习笔试真题 - 算法岗(3.12)

四季读书网 1 0
蚂蚁2026暑期实习笔试真题 - 算法岗(3.12)

蚂蚁2026春招笔试真题 - 算法岗(3.12)

AK机给大家整理了一份高质量的互联网大厂、大模型独角兽的内推和暑期实习/春秋招投递信息汇总,每日更新,建议收藏:
https://docs.qq.com/smartsheet/DUXJLSnZoTVFhVUVs?tab=sc_K49vrh


蚂蚁作为阿里系中独立笔试的公司,一直是算法岗同学的重点关注对象。AK机第一时间整理了这场蚂蚁26春招笔试的完整题解和代码,涵盖枚举、HMM Viterbi 和序列DP三个核心考点。

本场考试概述

考试时间:2026年3月12日
考试岗位:算法岗
难度评级:中等偏难

考点分析

  1. 1. 第一题:枚举/模拟(难度 Easy-Medium)
  2. 2. 第二题:HMM Viterbi 动态规划 + NumPy 实现(难度 Medium)
  3. 3. 第三题:序列DP + 贪心选择(难度 Hard)

建议策略

  1. 1. 第一题是分类讨论 + 枚举,想清楚三种情况即可,10-15分钟拿下
  2. 2. 第二题是经典 HMM Viterbi 算法的 NumPy 实现,熟悉概率图模型的同学可以快速搞定
  3. 3. 第三题是带约束的区间划分DP,需要预计算段贡献,难度较高但套路明确

备考推荐:蚂蚁属于阿里系公司,考点与阿里系笔试高度重合。推荐配合《阿里笔试速通指南》系统复习:第五章·动态规划(序列DP)对应第三题,第八章·AI/机器学习对应第二题的 NumPy 向量化实现。在线刷真题:https://www.neituiya.com/oj/7


第一题:增加or减少

题目描述

AK机有一个长度为  的数组 。AK机总共可以执行以下两种操作,其中第一种操作最多可执行一次,第二种操作也最多可执行一次(两种操作可以组合使用):

  1. 1. 选择两个不同的下标 ,将  修改为 ,花费  的代价。
  2. 2. 选择一个元素  以及任意正整数 ,对其增加或减少 ,花费  的代价。

AK机希望花费最小的代价,使得 

请输出这个最小代价。

输入描述

每个测试文件均包含多组测试数据。

第一行输入一个整数 ,表示数据组数。

每组测试数据描述如下:

第一行输入两个整数 

第二行输入  个整数 

保证单个测试文件中所有  的和不超过 

输出描述

对于每组测试数据,新起一行输出一个整数,表示最小花费代价。

样例1

输入

2
3 5
1 -2 3
4 10
0 0 0 -5

输出

1
0

样例解释

对于第一组测试数据只需要将  减一即可。

题解:模拟

题目问题拆解

给定长度为  的数组和操作代价 ,要求通过至多一次加法操作(代价 )和至多一次增减操作(代价为增减量)使乘积为 ,允许  枚举。

核心观察:乘积为  等价于至少存在一个 

算法实现

算法主策略:本题采用枚举所有操作组合取最优。

分三种情况讨论:

  1. 1. 如果数组中已有 ,答案直接为 
  2. 2. 仅用操作二:选  最小的元素直接变为 ,代价 
  3. 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(intinput().split())
    a = list(map(intinput().split()))
print(solve(a, k))

第二题:离散型隐马尔可夫模型预测

题目描述

请在仅使用  的前提下,实现离散型隐马尔可夫模型()的  动态规划。

给定:

初始分布 

状态转移矩阵 

观测概率矩阵 (列索引=观测符号)。

多条离散观测序列 ,符号取值 

请为每条序列计算:

  1. 1. 最优隐藏状态序列 
  2. 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. 1. 初始化
  2. 2. 递推:对 ,计算 ,同时记录 
  3. 3. 终止:最优路径概率 ,最终状态 
  4. 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(intinput().split())
    a = list(map(intinput().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 + 1for _ 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机的校招交流群

蚂蚁2026暑期实习笔试真题 - 算法岗(3.12) 第1张

抱歉,评论功能暂时关闭!