大厂真题 / 阿里巴巴

阿里 4.11 笔试真题 - AI研发岗

本场考试概述

考试时间:2026年4月11日 考试岗位:AI研发岗 难度评级:困难

考点分析

  1. 第一题:集合模拟(难度简单)
  2. 第二题:贪心构造(难度中等)
  3. 第三题:多选背包DP(难度困难)

建议策略

  1. 第一题直接模拟即可,注意 k=0 和 k=1 的边界
  2. 第二题关键在于发现插入值取 1 同时满足”被跳过”和”让下一个被保留”两个约束
  3. 第三题是本场难点,背包 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 修正量,逐行合并求最小移除总数