大厂真题 / 阿里巴巴
阿里 4.11 笔试真题 - AI研发岗
本场考试概述
考试时间:2026年4月11日 考试岗位:AI研发岗 难度评级:困难
考点分析:
- 第一题:集合模拟(难度简单)
- 第二题:贪心构造(难度中等)
- 第三题:多选背包DP(难度困难)
建议策略:
- 第一题直接模拟即可,注意 k=0 和 k=1 的边界
- 第二题关键在于发现插入值取 1 同时满足”被跳过”和”让下一个被保留”两个约束
- 第三题是本场难点,背包 DP 维护可达修正量集合,需要处理偏移量和活跃区间裁剪
第一题:模乘循环数
题目描述
初始时 a = 1。给定两个整数 k 和 m,系统不断执行 a = (a * k) % m。由于取模运算,a 的取值最终会进入循环。计算在无限次执行更新的过程中,a 一共可能取到多少个不同的值。
样例
输入
2 7
输出
3
思路分析
从 a=1 出发,反复执行 a = (a*k) % m,a 始终在 [0, m-1] 范围内。至多经过 m 步必然出现重复值,因此用集合记录出现过的值,当 a 再次出现时停止,集合大小即为答案。
题解代码
def solve(k, m):
seen = set()
a = 1
while a not in seen:
seen.add(a)
a = a * k % m
return len(seen)
k, m = map(int, input().split())
print(solve(k, m))
复杂度分析
时间复杂度:O(m),序列在 [0, m-1] 范围内最多经过 m 步即重复。 空间复杂度:O(m),集合存储所有不同取值。
第二题:逆转
题目描述
给出阈值 d 与最终保留序列 b(长度 n)。原序列按如下规则从左到右生成保留序列:先保留 b[0],处理到 a[i] 时,若 a[i] 与前一个保留元素的差的绝对值 <= d,则保留;否则跳过。
构造原序列 a,使得按规则生成的保留序列恰为 b,且 a 长度最小、在最小长度下字典序最小。
样例
输入
2
3 2
3 5 6
4 0
2 1 1 3
输出
4
3 5 1 6
5
2 1 1 1 3
思路分析
第一步:观察题目性质
| 考察保留序列相邻元素 b[j-1] 和 b[j]。若 | b[j-1] - b[j] | <= d,则 b[j] 可以直接紧跟 b[j-1] 被保留,无需插入。若 | b[j-1] - b[j] | > d(即 b[j-1] + d > b[j] 不成立时需要仔细判断方向),关键情况是当 b[j-1] + d > b[j] 时,b[j] 无法直接被保留。 |
第二步:问题转化
| 当 b[j-1] + d > b[j] 时(即前一个保留值加上阈值后仍然”够得到” b[j],但 b[j] 比 b[j-1] 小太多),需要在两者之间插入一个元素 x。x 需同时满足:(1) x 被跳过: | b[j-1] - x | > d;(2) b[j] 被保留: | x - b[j] | <= d。取 x = 1 可同时满足两个条件且字典序最小。 |
第三步:实现要点
从左到右扫描 b 的每对相邻元素,需要插入时插入值 1,每对至多插一个元素,总长度最多 2n-1。
题解代码
import sys
input = sys.stdin.readline
T = int(input())
out = []
for _ in range(T):
n, d = map(int, input().split())
b = list(map(int, input().split()))
a = [b[0]]
for j in range(1, n):
# b[j] 无法被 b[j-1] 直接保留,需要插入被跳过的值
if b[j - 1] + d > b[j]:
a.append(1)
a.append(b[j])
out.append(str(len(a)))
out.append(' '.join(map(str, a)))
print('\n'.join(out))
复杂度分析
时间复杂度:O(n),单次遍历 b 序列。 空间复杂度:O(n),存储构造的 a 序列。
第三题:果酱平衡
题目描述
有一个 n×m 的储物柜,格子里放着蓝莓酱(’B’)和草莓酱(’S’)。对每一行,可以独立选择移除最左侧的 k 瓶果酱(0 <= k <= m)。求最少拿走多少瓶果酱,使柜中剩余的 B 与 S 数量相等。
样例
输入
3
1 5
BSSSB
2 4
BSSB
SBBB
2 3
BBB
SSS
输出
3
4
0
思路分析
第一步:观察题目性质
每行有 m+1 种移除前缀长度可选(0 到 m),每种选择对应不同的”修正量”(移除的 B 数减去移除的 S 数)和”代价”(移除个数 k)。需要在所有行中各选一项,使修正量之和恰好消除初始不平衡。
第二步:问题转化
这本质上是多选背包 DP——把每行看成一组物品,从中恰好选一个,目标是凑出指定的总修正量且移除总数最小。
第三步:算法选择
设初始不平衡量 diff = 总 B 数 - 总 S 数。定义 dp[d] 为使各行修正值之和恰好等于 d 时的最小移除总数。逐行合并:对每行计算所有可能的 (修正量 delta, 代价 k) 对,同一个 delta 只保留最小 k,然后更新 DP 数组。最终答案为 dp[diff]。
第四步:实现要点
修正量范围为 [-nm, nm],用偏移量 OFFSET = n*m 映射到非负下标。维护活跃区间 [lo, hi] 避免遍历无效状态。
题解代码
import sys
input = sys.stdin.readline
def solve():
n, m = map(int, input().split())
grid = [input().strip() for _ in range(n)]
# 计算初始 B-S 差值
diff = sum(1 if c == 'B' else -1 for row in grid for c in row)
if diff == 0:
print(0)
return
MAX_D = n * m
SIZE = 2 * MAX_D + 1
OFFSET = MAX_D
INF = float('inf')
dp = [INF] * SIZE
dp[OFFSET] = 0
lo, hi = OFFSET, OFFSET
for i in range(n):
# 计算该行每种修正值 delta 所需的最小移除数 k
first_hit = {0: 0}
prefix = 0
for k in range(1, m + 1):
prefix += 1 if grid[i][k - 1] == 'B' else -1
if prefix not in first_hit:
first_hit[prefix] = k
row_lo = min(first_hit)
row_hi = max(first_hit)
new_lo = max(0, lo + row_lo)
new_hi = min(SIZE - 1, hi + row_hi)
new_dp = [INF] * SIZE
for delta, cost in first_hit.items():
src_lo = max(lo, -delta) if delta < 0 else lo
src_hi = min(hi, SIZE - 1 - delta) if delta > 0 else hi
src_lo = max(src_lo, 0)
src_hi = min(src_hi, SIZE - 1)
for d in range(src_lo, src_hi + 1):
nd = d + delta
if 0 <= nd < SIZE and dp[d] < INF:
nc = dp[d] + cost
if nc < new_dp[nd]:
new_dp[nd] = nc
dp = new_dp
lo, hi = new_lo, new_hi
target = OFFSET + diff
ans = dp[target] if 0 <= target < SIZE and dp[target] < INF else -1
print(ans)
T = int(input())
for _ in range(T):
solve()
复杂度分析
时间复杂度:O(n * m * D),其中 D 为 DP 活跃状态数(最大 n*m),每行选项数最多 m+1。 空间复杂度:O(n * m),DP 数组大小。
小结
- 第一题集合模拟,从 a=1 出发反复取模直到重复,集合大小即答案
- 第二题贪心构造,逐对分析保留序列相邻元素,不满足直接保留条件时插入值 1
- 第三题多选背包 DP 是本场难点,将每行视为一组物品,背包维度为 B-S 修正量,逐行合并求最小移除总数