【笔试刷题】阿里系列-2026.04.01-算法岗-改编真题

✅ 春招备战指南 ✅

💡 学习建议:

  • 先尝试独立解题
  • 对照解析查漏补缺

🧸 题面描述背景等均已深度改编,做法和题目本质基本保持一致。

🍹 感谢各位朋友们的订阅,你们的支持是我们创作的最大动力

🌸 目前本专栏已经上线200+套真题改编解析,后续会持续更新的

春秋招笔试机考招合集 -> 互联网必备刷题宝典🔗

阿里系列-2026.04.01-算法岗

三题的考点比较分散。第一题按模 分组后重排;第二题直接找离给定数最近的质数;第三题先把可压缩操作落到同字符连续段里,再做一次背包。

题目一:小兰的跨步换位重排

这题最容易误判为只能做局部调整。实际上,位置差为 的交换会把整个数组拆成若干条链,同一条链上的元素可以任意重排。理解这一点后,对每条链单独降序排列,再按原位置写回,就能得到字典序最大的答案。

难度:中等

题目二:离观测值最近的质数

数据范围不大,不需要上筛法。按距离从小到大向两侧枚举,配合试除法判质数,就能同时处理最近距离和同距离取较小值这两个条件。

难度:简单

题目三:二进制日志的定长压缩

这题容易卡在压缩段不能重叠这个限制上。顺着这个约束往下分析,会发现每个压缩段都只能落在某个同字符连续段里。因此,先分别计算每一段能带来的长度减少量,再做一次总背包合并即可。

难度:中等

1. 小兰的跨步换位重排

问题描述

说明:阿里系列近期多条业务线笔试题基本共用同一套公开机试,淘天、阿里云等方向都可参考本场。

小兰 在整理一组分散存放的数据块。数组里的元素排在一条长线上,但调度器只允许她做一种固定步长的交换:如果两个位置正好相差 ,就可以直接对调它们。

现在给定一个长度为 的整数数组 和一个正整数 。你可以进行任意次如下操作:

  • 选择一个下标 ,满足 ,将 交换。

请在可以无限次操作的前提下,求出最终能得到的字典序最大的数组。

数组字典序的比较方式如下:从第一个位置开始逐项比较,第一次出现不同的位置上,元素较大的那个数组字典序更大。

输入格式

每个测试文件包含多组测试数据。

  • 第一行输入一个整数 ,表示测试数据组数。
  • 对于每组测试数据:
    • 第一行输入两个整数
    • 第二行输入 个整数

输出格式

对于每组测试数据,输出一行 个整数,表示在这些交换操作下能够得到的字典序最大的数组。

样例输入

3
7 2
1 9 3 8 2 7 4
5 3
10 1 5 9 2
6 6
4 3 2 1 0 -1

样例输出

4 9 3 8 2 7 1
10 2 5 9 1
4 3 2 1 0 -1

数据范围

  • 所有测试数据中, 的总和不超过
样例 解释说明
样例1 第 1 组 位置会按模 分成两条链:。同一条链上的元素可以任意重排,所以把第一条链放成 ,第二条链保持 ,得到的整体字典序最大。
样例1 第 2 组 位置按模 分成三组:。分别把每组里的元素按从大到小放回原位置,答案就是
样例1 第 3 组 时,没有任何位置满足 ,因此数组无法变化。

题解

先看哪些位置之间真的能互相到达

一次操作只能交换位置差恰好为 的两个元素。于是下标会形成若干条独立的链:

  • ...

同一条链上的相邻点可以直接交换。因为相邻交换可以生成任意排列,所以同一条链上的元素最终可以任意重排。

而不同链之间永远不会连通,因此元素也不可能跨链移动。

为什么每条链都要按降序放回

字典序比较最关心前面的位置。

既然位置 只能从它所在的那条链里选元素,那么为了让整个数组字典序最大,位置 一定要拿到这条链里最大的元素;接下来同理,位置 要拿剩下元素里最大的;继续往后都一样。

所以每条链的最优策略就是:

  1. 取出这条链上的所有元素。
  2. 按从大到小排序。
  3. 再按链上的位置顺序依次放回。

把所有链都这样处理完,得到的就是全局字典序最大的数组。

复杂度分析

每个元素只会被分到一条链里一次,再参与一次排序。

  • 时间复杂度:
  • 空间复杂度:

参考代码

  • Python
import sys
input = lambda: sys.stdin.readline().strip()


def solve() -> None:
    t = int(input())
    out = []

    for _ in range(t):
        n, k = map(int, input().split())
        a = list(map(int, input().split()))
        ans = a[:]

        for st in range(k):
            pos = list(range(st, n, k))
            vals = [a[i] for i in pos]

            # 同一条链里的元素可以任意重排,按降序放回最优。
            vals.sort(reverse=True)

            for i, v in zip(pos, vals):
                ans[i] = v

        out.append(" ".join(map(str, ans)))

    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--) {
        int n, k;
        cin >> n >> k;

        vector<long long> a(n), ans(n);
        for (int i = 0; i < n; ++i) {
            cin >> a[i];
            ans[i] = a[i];
        }

        for (int st = 0; st < k; ++st) {
            vector<int> pos;
            vector<long long> vals;

            for (int i = st; i < n; i += k) {
                pos.push_back(i);
                vals.push_back(a[i]);
            }

            // 这条链上的元素可任意重排,降序放回即可让前面的位置尽量大。
            sort(vals.begin(), vals.end(), greater<long long>());

            for (int i = 0; i < (int) pos.size(); ++i) {
                ans[pos[i]] = vals[i];
            }
        }

        for (int i = 0; i < n; ++i) {
            if (i) {
                cout << ' ';
            }
            cout << ans[i];
        }
        cout << '\n';
    }
    return 0;
}
  • Java
import java.io.BufferedInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;

public class Main {
    static class FastScanner {
        private final BufferedInputStream in = new BufferedInputStream(System.in);
        private final byte[] buf = new byte[1 << 16];
        private int len = 0;
        private int ptr = 0;

        private int read() throws IOException {
            if (ptr >= len) {
                len = in.read(buf);
                ptr = 0;
                if (len <= 0) {
                    return -1;
                }
            }
            return buf[ptr++];
        }

        int nextInt() throws IOException {
            int c;
            do {
                c = read();
            } while (c <= ' ' && c != -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;
            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 out = new StringBuilder();

        int T = fs.nextInt();
        while (T-- > 0) {
            int n = fs.nextInt();
            int k = fs.nextInt();

            long[] a = new long[n];
            long[] ans = new long[n];
            for (int i = 0; i < n; ++i) {
                a[i] = fs.nextLong();
                ans[i] = a[i];
            }

            for (int st = 0; st < k; ++st) {
                ArrayList<Integer> pos = new ArrayList<>();
                ArrayList<Long> vals = new ArrayList<>();

                for (int i = st; i < n; i += k) {
                    pos.add(i);
                    vals.add(a[i]);
                }

                // 同链元素能任意排列,所以直接按降序放回。
                vals.sort(Collections.reverseOrder());

                for (int i = 0; i < pos.size(); ++i) {
                    ans[pos.get(i)] = vals.get(i);
                }
            }

            for (int i = 0; i < n; ++i) {
                if (i > 0) {
                    out.append(' ');
                }
                out.append(ans[i]);
            }
            out.append('\n');
        }

        System.out.print(out);
    }
}

2. 离观测值最近的质数

问题描述

小柯 在调试一台会产生整数读数的采样器。某次读数记成了整数 ,她想把这个值校准到离它最近的质数上,作为后续实验的标准刻度。

也就是说,她需要找出一个质数 ,使得 尽可能小。

如果有两个质数到 的距离相同,那么输出较小的那个。

输入格式

每个测试文件包含多组测试数据。

  • 第一行输入一个整数 ,表示测试数据组数。
  • 接下来 行,每行输入一个整数

输出格式

对于每组测试数据,输出一行一个整数,表示与 距离最近的质数。

样例输入

5
2
6
14
25
100

样例输出

2
5
13
23
101

数据范围

样例 解释说明
样例1 时,最近的两个质数是 ,距离都为 ,因此要输出更小的

题解

直接按距离向两边找

如果当前距离是 ,那么离 距离正好为 的候选数只有两个:

因为题目要求:

  1. 先让距离最小。
  2. 若距离相同,取更小的那个。

所以最自然的做法就是从 开始往外扩:

  1. 先检查 是否是质数。
  2. 如果不是,再检查 是否是质数。
  3. 一旦找到答案,立刻输出。

这样第一次找到的质数,一定就是最近的;而且同距离时总是先检查较小的那个,也正好满足题目的次级要求。

质数判定怎么做

因为 ,并且测试数据最多只有 组,没有必要做复杂预处理。

对于一个数

  • ,它不是质数。
  • 能被 整除,就只有 本身是质数。
  • 再往后只需要检查形如 的因子,直到 为止。

这个试除复杂度已经完全够用。

复杂度分析

设最终答案距离输入值的距离是

  • 每次候选判质的复杂度是
  • 总共大约会检查 个候选值。

在本题数据范围下,这个复杂度足够通过。

参考代码

  • Python
import sys
input = lambda: sys.stdin.readline().strip()


def is_prime(x: int) -> bool:
    if x < 2:
        return False
    # 先单独处理最小的两个质因子。
    if x % 2 == 0:
        return x == 2
    if x % 3 == 0:
        return x == 3

    # 之后只需要检查 6k±1 这两类候选因子。
    d = 5
    while d * d <= x:
        if x % d == 0 or x % (d + 2) == 0:
            return False
        d += 6
    return True


def solve() -> None:
    t = int(input())
    out = []

    for _ in range(t):
        x = int(input())
        d = 0

        while True:
            y = x - d
            # 同距离时要优先返回更小的那个,所以先检查 x-d。
            if y >= 2 and is_prime(y):
                out.append(str(y))
                break

            if d > 0:
                z = x + d
                # 只有较小端没命中时,才检查较大端。
                if is_prime(z):
                    out.append(str(z))
                    break

            d += 1

    sys.stdout.write("\n".join(out))


if __name__ == "__main__":
    solve()
  • Cpp
#include <bits/stdc++.h>
using namespace std;

bool is_prime(int x) {
    if (x < 2) {
        return false;
    }
    // 先特判最小的两个质因子。
    if (x % 2 == 0) {
        return x == 2;
    }
    if (x % 3 == 0) {
        return x == 3;
    }

    // 之后只检查 6k±1 形式的因子即可。
    for (long long d = 5; d * d <= x; d += 6) {
        if (x % d == 0 || x % (d + 2) == 0) {
            return false;
        }
    }
    return true;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int T;
    cin >> T;
    while (T--) {
        int x;
        cin >> x;

        for (int d = 0;; ++d) {
            int y = x - d;
            // 同距离时优先取更小的答案,所以先查左边。
            if (y >= 2 && is_prime(y)) {
                cout << y << '\n';
                break;
            }

            if (d > 0) {
                int z = x + d;
                // 左边没命中时,再检查右边。
                if (is_prime(z)) {
                    cout << z << '\n';
                    break;
                }
            }
        }
    }
    return 0;
}
  • Java
import java.io.BufferedInputStream;
import java.io.IOException;

public class Main {
    static class FastScanner {
        private final BufferedInputStream in = new BufferedInputStream(System.in);
        private final byte[] buf = new byte[1 << 16];
        private int len =

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

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

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

全部评论
写的很好呢,总结
点赞 回复 分享
发布于 04-03 23:15 北京
算法题越来越难了
点赞 回复 分享
发布于 04-03 22:40 辽宁

相关推荐

一共三面,最后似乎被砍hc了,过了两个月有hc了又给我打过一次电话,但已入职遂拒timeline:25.10.13一面-25.10.14二面-25.10.15&nbsp;hr面一面-25.10.13&nbsp;44min&nbsp;mt面自我介绍建模流程特征处理模型评估指标介绍简历与项目等面试官指出在回答问题的时候要有框架,我在回答的时候有点太细节了预测模型效果提升后对业务有什么价值所提到的几种机器学习方法优化的目标和损失函数sql水平,如何保证取出来的数是正确的面试过程中告知一面通过,二面主管需要更框架的回答二面-25.10.14&nbsp;58min&nbsp;主管面自我介绍(从与岗位的匹配度以及自身的优势等介绍)对自己挑战最大/最能体现自己优势的一个项目介绍如何保证数据填充的合理性(因为我上一个讲的相关的东西)如何向一个完全不懂的人介绍条件扩散模型为什么上一家实习只有3个月一个简单的sql题(但是很久没搞,基本乱答)(然后就一直揪着我sql的事...对sql怎么规划学习,一周时间怎么达到一个有一年经验的数据分析师的sql水平(无语)日常对ai的应用智商题:两个人分10w,通过什么方式会更公平反问:实习生培养机制两个小时后告知通过hr面-25.10.15&nbsp;12min自我介绍对企业和这个岗位的理解课程/实习时间项目是自己做的还是在上一家公司做的,为什么想要做这个贝壳这个实习会有多少帮助在校有没有参加社团、兴趣爱好基本聊天---------分割线:-----------后面再更新一些去年10月份和今年暑期面试的面经攒一下人品
查看23道真题和解析
点赞 评论 收藏
分享
评论
2
4
分享

创作者周榜

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