【笔试刷题】阿里系列-2026.03.25-研发岗-改编真题
✅ 春招备战指南 ✅
💡 学习建议:
- 先尝试独立解题
- 对照解析查漏补缺
🧸 题面描述背景等均已深度改编,做法和题目本质基本保持一致。
🍹 感谢各位朋友们的订阅,你们的支持是我们创作的最大动力
🌸 目前本专栏已经上线200+套真题改编解析,后续会持续更新的
春秋招笔试机考招合集 -> 互联网必备刷题宝典🔗
阿里系列-2026.03.25-研发岗
1. 小兰的仓位配货表
问题描述
说明:阿里系列近期多条业务线笔试题基本共用同一套公开机试,淘天、阿里云等方向都可参考本场。
小兰正在整理一份仓位配货表。现在有 类货物,第
类共有
件,需要分配给
个仓位。分配时需要满足:
- 每一类货物都必须分配完;
- 对于同一类货物,任意两个仓位拿到的数量之差不超过
。
在所有合法分配方案中,任选一个仓位,统计这个仓位最终拿到的货物总数。请输出该总数可能达到的最小值和最大值。
输入格式
每个测试文件包含多组数据。
- 第一行输入整数
(
),表示数据组数。
- 对于每组数据:
- 第一行输入两个整数
(
);
- 第二行输入
个整数
(
)。
保证单个测试文件内所有数据组的 之和不超过
。
输出格式
对于每组数据输出一行两个整数,分别表示单个仓位最终货物总数的最小值与最大值。
样例输入
3
3 2
5 2 7
4 3
0 0 0 0
2 5
10 1
样例输出
6 8
0 0
2 3
数据范围
题解
这题最容易想偏的地方,是把所有货物揉成一个总量去看。
题目的约束落在“每一种货物都要单独满足差值不超过 ”这一层。只要把这个限制拆开,整题就只剩下简单加法。
先只看某一类货物 。
把它分给 个仓位时,所有仓位的数量差不能超过
,因此分法已经基本被固定了:
- 每个仓位至少拿到
件;
- 若
,说明还有若干件“余量”,它们只能分散到若干个仓位里,并且每个仓位至多再多拿
件。
于是对某一个固定仓位来说,这一类货物的贡献只有两种:
- 拿到基础份额
;
- 或者在基础份额上再多拿
。
接下来把所有类别的贡献相加。
最小值怎么来
如果想让这个固定仓位拿到尽量少,那么每一类都只给它基础份额即可,因此最小值就是 。
最大值怎么来
如果某一类满足 ,就存在一个仓位可以在基础份额上再多拿
。
而不同类别之间互不影响,所以这些“额外的 ”完全可以都叠到同一个仓位身上。
因此最大值就是 。
这也解释了为什么不需要更复杂的贪心或 DP:
每一类货物的选择都是独立的,最后只是在做求和。
实现时线性扫描一次数组:
mn累加所有基础份额;extra统计有多少类存在余数。
单组时间复杂度是 ,总复杂度是
,空间复杂度
。
参考代码
- Python
import sys
def solve() -> None:
input = sys.stdin.readline
t = int(input().strip())
out = []
for _ in range(t):
# n 是货物种类数,k 是仓位数。
n, k = map(int, input().split())
arr = list(map(int, input().split()))
# mn: 固定仓位至少能拿到的总件数。
mn = 0
# extra: 有多少类货物还能让这个仓位再多拿 1 件。
extra = 0
for c in arr:
# 基础份额一定会落到每个仓位上。
mn += c // k
# 有余数时,说明某些仓位还能额外多拿 1 件。
if c % k != 0:
extra += 1
# 同一个仓位最多从每个“有余数的类别”里再多拿 1 件。
out.append(f"{mn} {mn + extra}")
# 按题目要求逐组输出答案。
sys.stdout.write("\n".join(out))
if __name__ == "__main__":
solve()
- Cpp
#include <bits/stdc++.h>
using namespace std;
using int64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
// T 组询问,逐组独立计算。
cin >> T;
while (T--) {
int n;
int64 k;
cin >> n >> k;
// mn: 单个仓位的保底总数。
int64 mn = 0;
// extra: 可以再额外 +1 的类别个数。
int64 extra = 0;
for (int i = 0; i < n; ++i) {
int64 c;
cin >> c;
// c / k 是这一类货物对任意仓位的基础贡献。
mn += c / k;
// 只要有余数,就存在仓位能再多拿 1 件。
if (c % k != 0) {
++extra;
}
}
// 最大值 = 基础份额总和 + 所有可额外贡献的类别数。
cout << mn << ' ' << mn + extra << '\n';
}
return 0;
}
- Java
import java.io.*;
import java.util.*;
public class Main {
static class FastScanner {
private final InputStream in = System.in;
private final byte[] buffer = new byte[1 << 16];
private int ptr = 0, len = 0;
private int read() throws IOException {
if (ptr >= len) {
len = in.read(buffer);
ptr = 0;
if (len <= 0) return -1;
}
return buffer[ptr++];
}
long nextLong() throws IOException {
int c;
do {
c = read();
} while (c <= ' ' && c != -1);
long sign = 1;
if (c == '-') {
sign = -1;
c = read();
}
long val = 0;
while (c > ' ') {
val = val * 10 + (c - '0');
c = read();
}
return val * sign;
}
}
public static void main(String[] args) throws Exception {
FastScanner fs = new FastScanner();
StringBuilder sb = new StringBuilder();
int t = (int) fs.nextLong();
while (t-- > 0) {
int n = (int) fs.nextLong();
long k = fs.nextLong();
// mn 表示固定仓位至少能分到多少件。
long mn = 0;
// extra 表示还能多拿 1 件的货物类别数。
long extra = 0;
for (int i = 0; i < n; i++) {
long c = fs.nextLong();
// 这一类货物平均分下来的基础份额。
mn += c / k;
// 有余数,说明某个仓位可以再多拿 1。
if (c % k != 0) extra++;
}
// 输出单个仓位可能达到的最小值和最大值。
sb.append(mn).append(' ').append(mn + extra).append('\n');
}
// 统一输出,避免多次打印带来的额外开销。
System.out.print(sb);
}
}
2. 小柯的双榜对齐
问题描述
说明:阿里系列近期多条业务线笔试题基本共用同一套公开机试,淘天、阿里云等方向都可参考本场。
小柯手上有两份候选榜单,分别记为排列 和
,长度都为
,元素都是
且互不重复。她想从两份榜单里挑出一条公共子序列,并让这条子序列的字典序尽可能大。请你输出这条序列。
定义说明:
- 字典序:从左到右比较第一个不同位置,数值更大的序列字典序更大;若一个是另一个的前缀,则更长者更大。
- 子序列:从原序列删除若干元素(可为
个)后保持相对顺序得到的序列。
输入格式
- 第一行输入整数
(
)。
- 第二行输入
个整数
,表示排列
。
- 第三行输入
个整数
,表示排列
。
保证 都是
的排列。
输出格式
- 第一行输出一个整数
,表示答案序列长度。
- 第二行输出
个整数,表示字典序最大的公共子序列。
样例输入
5
2 5 3 1 4
5 2 4 3 1
样例输出
2
5 4
数据范围
均为
的排列
题解
这题表面上像公共子序列,很多人会下意识往 DP 想。
但这里有个很关键的限制: 和
都是排列,每个值只出现一次。这个条件把问题简化得非常彻底。
先把“公共子序列”翻译成位置条件
设:
posP[x]表示值在排列
中的位置;
posQ[x]表示值在排列
中的位置。
如果当前答案最后一个数在两边的位置分别是 (curP, curQ),那么下一个值 能接进答案,当且仅当
且
。
因为排列里没有重复值,所以一个值要么能接,要么不能接,不会出现“同一个值在后面还有别的位置可选”的情况。
字典序最大意味着什么
字典序比较时,先看第一位;第一位相同,再看第二位;依次类推。
所以只要当前位置有更大的值能选,就绝对不能先拿更小的值。
这就把整题变成了一个非常直接的贪心:
- 预处理每个值在两个排列中的位置。
- 维护当前已经选到哪里,记为
(curP, curQ)。 - 按值从大到小枚举
x = n, n-1, ..., 1。 - 若
x在两个排列中的位置都还在当前末尾之后,就立刻把它加入答案。
为什么这个贪心成立
假设当前有两个可选值 。
- 如果先选
,那么答案在这一位就已经比“先选
”更小了;
- 后面无论补什么,都无法弥补第一个分歧位置吃掉的损失。
因此当前位置一定要优先拿最大的可行值。
而一旦选定了这个最大值,后面的要求仍然完全一样:
只是在两个排列中,把可选范围缩到了更靠后的后缀里。于是同样的贪心可以继续做下去。
为什么不用 DP
普通 LCS 之所以麻烦,是因为有重复值,同一个字符可以有很多种对齐方式。
本题里每个值只出现一次,状态压缩成了“当前位置之后还能不能接上”。所以只用一次从大到小的扫描就够了。
时间复杂度 ,空间复杂度
。
参考代码
- Python
import sys
def solve() -> None:
input = sys.stdin.readline
n = int(input().strip())
p = list(map(int, input().split()))
q = list(map(int, input().split()))
# pos_p[x] / pos_q[x] 记录值 x 在两份排列中的位置。
pos_p = [0] * (n + 1)
pos_q = [0] * (n + 1)
for i, x in enumerate(p, 1):
# 记录每个值在第一份排列中的位置。
pos_p[x] = i
for i, x in enumerate(q, 1):
# 记录每个值在第二份排列中的位置。
pos_q[x] = i
# 当前答案最后一个值在两边对应的位置。
cur_p = 0
cur_q = 0
# ans 按加入顺序存放最终答案。
ans = []
for x in range(n, 0, -1):
# 只有当 x 在两边都还能接到当前答案末尾之后,才能加入公共子序列。
if pos_p[x] > cur_p and pos_q[x] > cur_q:
ans.append(x)
cur_p = pos_p[x]
cur_q = pos_q[x]
# 题目要求先输出长度,再输出具体序列。
print(len(ans))
print(*ans)
if __name__ == "__main__":
solve()
- Cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
// posP[x] / posQ[x] 记录值 x 在两份排列中的出现位置。
vector<int> posP(n + 1), posQ(n + 1);
for (int i = 1; i <= n; ++i) {
int x;
cin >> x;
// 值 x 在 p 中只出现一次,直接记位置。
posP[x] = i;
}
for (int i = 1; i <= n; ++i) {
int x;
cin >> x;
// 值 x 在 q 中只出现一次。
posQ[x] = i;
}
// 当前答案末尾在 p、q 中分别停在哪里。
int curP = 0, curQ = 0;
// ans 顺序保存最终公共子序列。
vector<int> ans;
ans.reserve(n);
for (int x = n; x >= 1; --x) {
// x 若能同时接在两边当前末尾之后,就应该优先选它。
if (posP[x] > curP && posQ[x] > curQ) {
ans.push_back(x);
// 一旦选了 x,后续值必须同时出现在这两个位置之后。
curP = posP[x];
curQ = posQ[x];
}
}
// 输出答案长度和具体序列。
cout << ans.size() << '\n';
for (int i = 0; i < (int)ans.size(); ++i) {
if (i) cout << ' ';
cout << ans[i];
}
cout << '\n';
return 0;
}
- Java
import java.io.*;
import java.util.*;
public class Main {
static class FastScanner {
private final InputStream in = System.in;
private final byte[] buffer = new byte[1 << 16];
private int ptr = 0, len = 0;
private int read() throws IOException {
if (ptr >= len) {
len = in.read(buffer);
ptr = 0;
if (len <= 0) return -1;
}
return buffer[ptr++];
}
int nextInt()
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
互联网刷题笔试宝典,这里涵盖了市面上大部分的笔试题合集,希望助大家春秋招一臂之力

查看10道真题和解析