【笔试刷题】阿里系列-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%内容,订阅专栏后可继续查看/也可单篇购买

互联网刷题笔试宝典 文章被收录于专栏

互联网刷题笔试宝典,这里涵盖了市面上大部分的笔试题合集,希望助大家春秋招一臂之力

全部评论

相关推荐

03-16 11:07
南开大学 Java
牛马人的牛马人生:快手卡实习经历的
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务