【笔试刷题】阿里系列-2026.03.28-算法岗-改编真题
✅ 春招备战指南 ✅
💡 学习建议:
- 先尝试独立解题
- 对照解析查漏补缺
🧸 题面描述背景等均已深度改编,做法和题目本质基本保持一致。
🍹 感谢各位朋友们的订阅,你们的支持是我们创作的最大动力
🌸 目前本专栏已经上线200+套真题改编解析,后续会持续更新的
春秋招笔试机考招合集 -> 互联网必备刷题宝典🔗
阿里系列-2026.03.28-算法岗
1. 小基 的回合净值对抗
先看单个位置对答案 的贡献。无论这个位置最后被谁拿走,贡献都是
。
因此整题只剩一次求和,顺序和策略都不会影响结果。
2. 小兰的双平方切分统计
先把不超过 的完全平方数全部枚举出来。
对每个平方数尝试切一刀,检查左右两段是不是也都是平方数,最后对结果表二分回答询问。
3. 小柯的差值阈值最长子段
把相邻差值看成边权,阈值 固定后,问题就是“保留权值不超过
的边时,链上最大连通块有多大”。
阈值从小到大扫一遍,用分桶 + 并查集增量合并即可。
01. 小基 的回合净值对抗
问题描述
给定两个长度为 的整数数组
,共有
个位置可选,各自只能被选择一次。Alice 先手、Bob 后手,轮流选位。
- Alice 选择位置
时,本回合净收益为
。
- Bob 选择位置
时,本回合净收益为
。
设 Alice 的总净收益为 ,Bob 的总净收益为
。两人都采取最优策略,Alice 想最大化
,Bob 想最小化
。请输出最终分差
。
输入格式
每个测试文件包含多组测试数据。
- 第一行输入一个整数
,表示测试数据组数。
- 对于每组测试数据:
- 第一行输入一个整数
。
- 第二行输入
个整数
。
- 第三行输入
个整数
。
输出格式
对于每组测试数据,输出一行一个整数,表示在最优博弈下的分差 。
样例输入
3
3
3 1 2
2 2 2
2
1 10
10 1
4
5 1 1 1
1 4 4 4
样例输出
0
0
-5
数据范围
- 所有测试数据中
的总和不超过
题解
先别管“双方最优”这层外壳,先看每个位置对最终答案的贡献。
记
我们只看某个位置 对最终答案
的贡献。
- 如果这个位置被 Alice 选走,那么
增加
,所以
增加
。
- 如果这个位置被 Bob 选走,那么 Bob 本回合净收益是
,也就是
增加
。于是
变成
,仍然增加
。
也就是说,不管这个位置最后落在谁手里,它对答案的贡献都固定是 。
每个位置都这样,整道题就只剩把这些贡献加起来。
所以答案直接就是
每组数据线性扫描一次即可,时间复杂度是 ,总复杂度是
。
参考代码
- Python
import sys
def solve() -> None:
data = sys.stdin.buffer.read().split()
if not data:
return
t = int(data[0])
idx = 1
out = []
for _ in range(t):
n = int(data[idx])
idx += 1
# 先累加数组 a。
sum_a = 0
for _ in range(n):
sum_a += int(data[idx])
idx += 1
# 再累加数组 b。
sum_b = 0
for _ in range(n):
sum_b += int(data[idx])
idx += 1
# 每个位置的贡献都是固定的,所以答案就是总差值。
out.append(str(sum_a - sum_b))
sys.stdout.write("\n".join(out))
if __name__ == "__main__":
solve()
- Cpp
#include
using namespace std;
using int64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
int64 sum_a = 0;
int64 sum_b = 0;
for (int i = 0; i < n; ++i) {
int64 x;
cin >> x;
sum_a += x;
}
for (int i = 0; i < n; ++i) {
int64 x;
cin >> x;
sum_b += x;
}
// 答案等于 sum(a_i - b_i)。
cout << (sum_a - sum_b) << '\n';
}
return 0;
}
- Java
import java.io.*;
public class Main {
public static void main(String[] args) throws Exception {
FastScanner fs = new FastScanner(System.in);
StringBuilder sb = new StringBuilder();
int t = fs.nextInt();
while (t-- > 0) {
int n = fs.nextInt();
long sumA = 0;
long sumB = 0;
for (int i = 0; i < n; i++) {
sumA += fs.nextLong();
}
for (int i = 0; i < n; i++) {
sumB += fs.nextLong();
}
// 每个位置对 A-B 的贡献固定,因此直接求总和。
sb.append(sumA - sumB).append('\n');
}
System.out.print(sb);
}
static class FastScanner {
private final InputStream in;
private final byte[] buffer = new byte[1 << 16];
private int ptr = 0;
private int len = 0;
FastScanner(InputStream is) {
in = is;
}
private int read() throws IOException {
if (ptr >= len) {
len = in.read(buffer);
ptr = 0;
if (len <= 0) {
return -1;
}
}
return buffer[ptr++];
}
int nextInt() throws IOException {
return (int) nextLong();
}
long nextLong() throws IOException {
int c;
while ((c = read()) <= ' ') {
if (c == -1) {
return -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;
}
}
}
02. 小兰的平方拼接计数
问题描述
一个正整数称为“平方拼接数”当且仅当它自身是完全平方数,且某个相邻切分可以把十进制表示分成左右两段,两段都还是完全平方数。
- 它本身是一个完全平方数。
- 它的十进制表示可以在某个相邻位置切成左右两段,且这两段都非空,并且对应的十进制整数也都是完全平方数。
切分时不允许出现前导零,除非这一段本身就只有一个字符 0。
例如, 是一个平方拼接数,因为它可以切成
,其中
都是完全平方数。
给定上界 ,请你统计不超过
的平方拼接数有多少个。
输入格式
每个测试文件包含多组测试数据。
- 第一行输入一个整数
,表示数据组数。
- 接下来
行,每行输入一个整数
。
输出格式
对于每组数据,输出一行一个整数,表示不超过 的平方拼接数个数。
样例输入
2
50
1500
样例输出
1
5
数据范围
题解
查询次数很少,而上界只有 ,这题最自然的做法就是先把所有候选数预处理出来。
因为一个平方拼接数自己必须先是完全平方数,所以我们只需要枚举
一共也就三万多个数。
对于某个平方数 ,把它转成字符串
。如果长度是
,那么只可能在
这些位置切开。
假设切点在 ,那么:
- 左半段是
s[0:p] - 右半段是
s[p:L]
我们需要检查三件事:
- 左段没有非法前导零;
- 右段没有非法前导零;
- 左右两段转成整数后,都是完全平方数。
只要某个切点合法,这个 就应该计入答案。
预处理完全部平方拼接数后,把它们放进有序数组里。
每次询问只要求不超过 的个数,直接用二分求出最后一个
的位置即可。
复杂度分析
- 预处理时只枚举约
个平方数,每个数最多尝试十次切分,复杂度近似
。
- 每次查询二分一次,复杂度
,其中
是平方拼接数的总个数。
完全够用。
参考代码
- Python
import bisect
import math
import sys
def is_square(x: int) -> bool:
if x < 0:
return False
r = math.isqrt(x)
return r * r == x
def build_good_numbers() -> list[int]:
limit = int(math.isqrt(10**9))
good = []
for i in range(1, limit + 1):
sq = i * i
s = str(sq)
if len(s) < 2:
continue
ok = False
for p in range(1, len(s)):
left_s = s[:p]
right_s = s[p:]
# 两段都不能出现非法前导零。
if len(left_s) > 1 and left_s[0] == "0":
continue
if len(right_s) > 1 and right_s[0] == "0":
continue
left = int(left_s)
right = int(right_s)
if is_square(left) and is_square(right):
ok = True
break
if ok:
good.append(sq)
return good
GOOD = build_good_numbers()
def solve() -> None:
data = sys.stdin.buffer.read().split()
if not data:
return
t = int(data[0])
out = []
for i in range(1, t + 1):
n = int(data[i])
# 二分统计 <= n 的个数。
out.append(str(bisect.bisect_right(GOOD, n)))
sys.stdout.write("\n".join(out))
if __name__ == "__main__":
solve()
- Cpp
#include
using namespace std;
using int64 = long long;
bool isSquare(int x) {
if (x < 0) {
return false;
}
int r = (int) sqrt((long double) x);
while (1LL * (r + 1) * (r + 1) <= x) {
++r;
}
while (1LL * r * r > x) {
--r;
}
return 1LL * r * r == x;
}
vector buildGoodNumbers() {
vector good;
int limit = (int) sqrt(1e9);
for (int i = 1; i <= limit; ++i) {
int sq = i * i;
string s = to_string(sq);
if ((int) s.size() < 2) {
continue;
}
bool ok = false;
for (int p = 1; p < (int) s.size(); ++p) {
string leftStr = s.substr(0, p);
string rightStr = s.substr(p);
// 检查左右两段是否有非法前导零。
if ((int) leftStr.size() > 1 && leftStr[0] == '0') {
continue;
}
if ((int) rightStr.size() > 1 && rightStr[0] == '0') {
continue;
}
int left = stoi(leftStr);
int right = stoi(rightStr);
if (isSquare(left) && isSquare(right)) {
ok = true;
break;
}
}
if (ok) {
good.push_back(sq);
}
}
return good;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
vector good = buildGoodNumbers()
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
互联网刷题笔试宝典,这里涵盖了市面上大部分的笔试题合集,希望助大家春秋招一臂之力
查看3道真题和解析