【笔试刷题】阿里系列-2026.03.25-算法岗-改编真题
✅ 春招备战指南 ✅
💡 学习建议:
- 先尝试独立解题
- 对照解析查漏补缺
🧸 题面描述背景等均已深度改编,做法和题目本质基本保持一致。
🍹 感谢各位朋友们的订阅,你们的支持是我们创作的最大动力
🌸 目前本专栏已经上线200+套真题改编解析,后续会持续更新的
春秋招笔试机考招合集 -> 互联网必备刷题宝典🔗
阿里系列-2026.03.25-算法岗
1. 小基 的同余构造
问题描述
说明:阿里系列近期多条业务线笔试题基本共用同一套公开机试,淘天、阿里云等方向都可参考本场。
小基 正在设计一组双标号规则。给定一个正整数 ,请你构造两个不同的正整数
,满足:
如果不存在这样的 ,输出
。
输入格式
第一行一个整数 ,表示测试组数。
接下来 行,每行一个整数
。
输出格式
每组数据输出一行:
- 无解输出
-1 - 有解输出两个整数
样例输入
3
1
8
15
样例输出
-1
1 2
14 7
数据范围
题解
这题是很典型的构造题。
要点在于先找到能覆盖全部情况的固定构造,而不是去枚举候选答案。
目标是找到两个不同的正整数 ,满足
。
1. 先排掉无解的小范围
当 时,可选的正整数太少:
时,根本没有合法的
;
时,只有
一个候选;
时,只有
,对应余数分别是
。
所以这三种情况都无解。
2. 偶数直接用最简单的构造
当 是偶数且
时,取
。
因为 。
两边余数完全相同,这组解立刻成立。
3. 奇数也有固定构造
当 是奇数且
时,取
。
设 ,其中
。那么
,并且
。
同样得到相等的余数。
为什么这样已经覆盖全部情况
除了 这几种无解情形,其余整数一定属于“偶数”或“奇数”其中一种:
- 偶数用
;
- 奇数用
。
所以整题只需要分类讨论,完全不需要搜索。
每组数据只做常数次判断与输出,时间复杂度 ,总复杂度
。
参考代码
- Python
import sys
def solve() -> None:
data = sys.stdin.buffer.read().split()
if not data:
return
t = int(data[0])
out = []
idx = 1
for _ in range(t):
# 直接从缓冲区中顺序读取每组 n。
n = int(data[idx])
idx += 1
# n <= 3 时,候选数不够,无法找出两组不同的 x 和 y。
if n <= 3:
out.append("-1")
# 偶数直接用 (1, 2),两边余数都是 0。
elif n % 2 == 0:
out.append("1 2")
# 奇数用 (n-1, (n-1)//2),两边余数都是 1。
else:
out.append(f"{n - 1} {(n - 1) // 2}")
# 每组答案独立输出一行。
sys.stdout.write("\n".join(out))
if __name__ == "__main__":
solve()
- Cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
long long n;
cin >> n;
// 小范围无解,直接输出 -1。
if (n <= 3) {
cout << -1 << '\n';
// 偶数固定构造 (1, 2)。
} else if ((n & 1LL) == 0) {
cout << 1 << ' ' << 2 << '\n';
// 奇数固定构造 (n-1, (n-1)/2)。
} else {
cout << (n - 1) << ' ' << ((n - 1) / 2) << '\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) {
// 每组只需要按 n 的奇偶性决定输出哪套构造。
long n = fs.nextLong();
// n <= 3 时没有两组不同的合法候选。
if (n <= 3) {
sb.append("-1\n");
// 偶数情况固定输出一组最简单的解。
} else if ((n & 1L) == 0L) {
sb.append("1 2\n");
// 奇数情况用题解中的构造公式。
} else {
sb.append(n - 1).append(' ').append((n - 1) / 2).append('\n');
}
}
// 汇总输出,保持 ACM 模式。
System.out.print(sb);
}
static class FastScanner {
private final InputStream in;
private final byte[] buffer = new byte[1 << 16];
private int ptr = 0, 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 {
int c;
while ((c = read()) <= ' ') {
if (c == -1) return -1;
}
int sign = 1;
if (c == '-') {
sign = -1;
c = read();
}
int val = 0;
while (c > ' ') {
val = val * 10 + c - '0';
c = read();
}
return val * sign;
}
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;
}
}
}
2. 小兰的棋盘积分赛
问题描述
说明:阿里系列近期多条业务线笔试题基本共用同一套公开机试,淘天、阿里云等方向都可参考本场。
小兰准备了一场棋盘积分赛。给定一个 的字符棋盘,每个格子是
'0' 或 '1'。棋子初始在左上角 ,两名玩家轮流操作,先手先走,每次只能向右或向下移动一步。
若本次移动到的新格子是 '1',当前玩家得分 ;若是
'0',得分 。初始格子不计分。棋子到达右下角
后游戏结束。
两人都采用最优策略,求最终的 。
输入格式
第一行输入两个整数 。
接下来 行,每行一个长度为
的 01 串。
输出格式
输出一个整数,表示最优博弈下的分差(先手减后手)。
样例输入 1
2 2
01
11
样例输出 1
0
样例输入 2
1 3
101
样例输出 2
-2
数据范围
题解
这题一眼看上去像普通路径 DP,但只算路径还不够。
每一步的分数由“当前轮到谁落子”决定,所以首先要弄清:走到某个格子时,到底该由谁做选择。
设 表示:
棋子当前停在
,从这一格走到终点,在双方都最优时,最终能得到的
先手总分 - 后手总分
终点 已经不能再继续走,而且题目计分发生在“走到下一格”时,所以终点本身不再额外产生贡献。于是有
。
1. 判断当前轮到谁走
从 走到
,总共走了
步。
- 如果这个步数是偶数,说明接下来轮到先手
- 如果这个步数是奇数,说明接下来轮到后手
等价地说:
为偶数时,轮到先手
为奇数时,轮到后手
2. 定义走到下一格的收益
若下一步走到某个格子 next:
- 若该格子是
'1',本次行动玩家得分 - 若该格子是
'0',本次行动玩家得分
记这个收益为 :落到
'1' 时是 ,落到
'0' 时是 。
3. 状态转移
若当前轮到先手,先手想让 先手分 - 后手分 尽量大,所以状态转移就是在所有后继里取 。
若当前轮到后手,后手拿到的分数会让总分差减少,因此状态转移就是在所有后继里取 。
这里减号特别容易写反。
因为 dp 存的是“先手减后手”,后手当前多拿到 分,对整体分差来说就是减掉
。
4. 为什么从右下往左上推
每个状态只依赖“向右走”和“向下走”这两个后继状态,天然适合逆推。
因此按右下到左上的顺序填表即可,最后的 就是从起点开始时的最优分差。
时间复杂度 ,空间复杂度
。
参考代码
- Python
import sys
def solve() -> None:
input = sys.stdin.readline
n, m = map(int, input().split())
# g[i][j] 表示第 i 行第 j 列的字符。
g = [input().strip() for _ in range(n)]
# dp[i][j] = 从 (i,j) 出发时,先手分 - 后手分 的最优值。
dp = [[0] * m for _ in range(n)]
for i in range(n - 1, -1, -1):
for j in range(m - 1, -1, -1):
if i == n - 1 and j == m - 1:
continue
# (i + j) 为偶数时,说明这一手轮到先手。
first_turn = ((i + j) % 2 == 0)
if first_turn:
best = -10**18
if j + 1 < m:
# 走到右边这一格后,本次行动立即得到对应分数。
w = 1 if g[i][j + 1] == "1" else -1
best = max(best, dp[i][j + 1] + w)
if i + 1 < n:
w = 1 if g[i + 1][j] == "1" else -1
best = max(best, dp[i + 1][j] + w)
else:
best = 10**18
if j + 1 < m:
# 轮到后手时,后手得分要从“先手减后手”里扣掉。
w = 1 if g[i][j + 1] == "1" else -1
best = min(best, dp[i][j + 1] - w)
if i + 1 < n:
w = 1 if g[i + 1][j] == "1" else -1
best = min(best, dp[i + 1][j] - w)
dp[i][j] = best
# 从起点开始时的最优分差。
print(dp[0][0])
if __name__ == "__main__":
solve()
- Cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
if (!(cin >> n >> m)) return 0;
vector<string> g(n);
// 读入整张棋盘。
for (int i = 0; i < n; i++) cin >> g[i];
// dp[i][j] 表示从 (i,j) 开始时的最优分差。
vector<vector<int>> dp(n, vector<int>(m, 0));
for (int i = n - 1; i >= 0; --i) {
for (int j = m - 1; j >= 0; --j) {
if (i == n - 1 && j == m - 1) continue;
// 当前格子的行动方只由步数奇偶决定。
bool firstTurn = ((i + j) % 2 == 0);
int best = firstTurn ? INT_MIN : INT_MAX;
if (j + 1 < m) {
// 向右走后的即时得分。
int w = (g[i][j + 1] == '1' ? 1 : -1);
int cand = firstTurn ? (dp[i][j + 1] + w) : (dp[i][j + 1] - w);
if (firstTurn) best = max(best, cand);
else best = min(best, cand);
}
if (i + 1 < n) {
// 向下走后的即时得分。
int w = (g[i + 1][j] == '1' ? 1 : -1);
int cand = firstTurn ? (dp[i + 1]
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
互联网刷题笔试宝典,这里涵盖了市面上大部分的笔试题合集,希望助大家春秋招一臂之力
