【笔试刷题】携程-2026.03.29-算法岗-改编真题

✅ 春招备战指南 ✅

💡 学习建议:

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

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

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

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

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

携程-2026.03.29-算法岗

这套算法岗和开发岗共享了 3 道通用题,中间夹了一道更偏计算实现的注意力题。前两题还是热身节奏,第 3 题需要把整套流程按矩阵规则完整落地,第 4 题继续是数论跳跃。

题目一:字母 a 的位置

固定长度、固定字符集,直接顺扫即可,是明显的热身题。

难度:Low

题目二:实时榜单

范围只有 ,维护每人每题最高分再暴力统计排名就够了,不需要额外数据结构。

难度:Low

题目三:双门控加权器

这题核心不在“想算法”,而在把门控、Top-k、归一化和最终乘法一条链完整实现出来。边界主要在 s_max \le 0 和 Top-k 并列处理。

难度:Mid

题目四:min-gcd 迭代器

不变的一整段连续迭代压成一次跳跃,是这题唯一真正能过大数据的做法。

难度:High

1. 字母 a 的位置

问题描述

小基 拿到了一段长度恰好为 的标签串。

这段字符串只会由 abcde 这五个字符各出现一次组成,顺序可能被打乱。现在需要找出字符 a 出现在第几个位置。

位置从 开始计数。

输入格式

输入一行,一个长度为 的字符串

保证字符串中恰好包含一个 a、一个 b、一个 c、一个 d、一个 e

输出格式

输出一个整数,表示字符 a 的位置。

样例输入 1

abcde

样例输出 1

1

数据范围

  • 字符串只由 abcde 组成
  • 这五个字符各出现且只出现一次
样例 解释说明
样例1 字符串的第 个字符就是 a,所以答案为

题解

这题没有任何隐藏条件,顺着扫一遍字符串就够了。

因为字符串长度固定为 ,所以直接从左到右枚举下标:

  1. 如果当前字符不是 a,继续往后看。
  2. 一旦遇到 a,立刻输出当前位置编号。

位置从 开始计数,而程序里的下标通常从 开始,所以输出时记得加

时间复杂度是 ,也就是常数复杂度。空间复杂度是

参考代码

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


def solve():
    s = input()

    # 顺着找一遍,遇到 a 就输出 1-based 位置。
    for i, ch in enumerate(s):
        if ch == "a":
            print(i + 1)
            return


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

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

    string s;
    cin >> s;

    // 长度固定为 5,直接线性扫描即可。
    for (int i = 0; i < 5; i++) {
        if (s[i] == 'a') {
            cout << i + 1 << '\n';
            return 0;
        }
    }
    return 0;
}
  • Java
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String s = br.readLine().trim();

        // 逐个字符检查,找到 a 后输出位置。
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == 'a') {
                System.out.println(i + 1);
                return;
            }
        }
    }
}

2. 实时榜单

问题描述

小基 正在盯一场 赛制训练赛的实时榜单。

比赛里一共有若干道互相独立的题。每名选手对同一道题可以提交很多次,但记分时只保留这道题的最高得分;选手总分等于自己所有题目最高分之和。

现在按时间顺序给出 次提交记录。第 次提交是三个整数 ,表示编号为 的选手在编号为 的题目上拿到了 分。

每处理完一条提交记录,都要输出编号为 的选手当前排名。

排名规则如下:

  • 总分更高的选手排名更靠前。
  • 总分相同的选手并列,且并列会占用名次。

例如分数从高到低依次为 ,那么对应名次就是

输入格式

第一行输入一个整数 ,表示提交记录条数。

接下来 行,每行输入三个整数 ,表示一条提交记录。

输出格式

输出 行。

行输出一个整数,表示处理完前 条记录后,编号为 的选手排名。

样例输入 1

10
2 1 0
1 1 80
3 2 100
1 2 60
3 1 40
3 3 60
1 1 90
5 1 100
5 2 100
1 4 50

样例输出 1

1
1
2
1
1
2
2
2
3
1

数据范围

样例 解释说明
样例1 次提交后,编号为 的选手在第 题上的最高分从 提升到 ,总分随之变化,因此排名也会重新计算。

题解

题目里的范围其实已经把做法写得很明显了:选手编号最多只有 ,题号最多也只有

所以根本不需要平衡树或堆,直接维护两张表就够了:

  • best[u][p]:选手 在题目 上的历史最高分。
  • sum[u]:选手 当前总分。

当读到一条新提交 时:

  1. 如果 ,说明这次没有刷新这道题的最高分,总分不变。
  2. 如果 ,就把总分增加 c - best[a][b],再更新 best[a][b]

接下来只要统计有多少名选手的总分严格大于 sum[1]

因为并列要占名次,所以编号为 的选手排名就是:

为什么这样对?

  • 总分更高的人一定排在前面。
  • 总分相同的人和编号为 并列,不会把他的名次继续往后推。

每次提交后扫描一次 名选手就行,因此总时间复杂度是 ,在这里完全够用。空间复杂度是

参考代码

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


def solve():
    n = int(input())

    # best[u][p] 记录选手 u 在题目 p 上的最高分。
    best = [[0] * 101 for _ in range(101)]
    total = [0] * 101

    out = []
    for _ in range(n):
        u, p, sc = map(int, input().split())

        # 只有刷新最高分时,总分才会变大。
        if sc > best[u][p]:
            total[u] += sc - best[u][p]
            best[u][p] = sc

        my_score = total[1]
        rank = 1
        for i in range(1, 101):
            if total[i] > my_score:
                rank += 1
        out.append(str(rank))

    print("\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 n;
    cin >> n;

    // best[u][p]:选手 u 在题目 p 上的最高分。
    int best[101][101] = {};
    int sum[101] = {};

    while (n--) {
        int u, p, sc;
        cin >> u >> p >> sc;

        // 如果这次提交更好,就把差值补到总分里。
        if (sc > best[u][p]) {
            sum[u] += sc - best[u][p];
            best[u][p] = sc;
        }

        int rank = 1;
        for (int i = 1; i <= 100; i++) {
            if (sum[i] > sum[1]) {
                rank++;
            }
        }
        cout << rank << '\n';
    }
    return 0;
}
  • Java
import java.io.BufferedInputStream;

public class Main {
    static class FastScanner {
        private final byte[] buf = new byte[1 << 16];
        private int len = 0;
        private int ptr = 0;

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

        int nextInt() throws Exception {
            int c;
            do {
                c = read();
            } while (c <= ' ' && c != -1);
            int sgn = 1;
            if (c == '-') {
                sgn = -1;
                c = read();
            }
            int x = 0;
            while (c > ' ') {
                x = x * 10 + c - '0';
                c = read();
            }
            return x * sgn;
        }
    }

    public static void main(String[] args) throws Exception {
        FastScanner fs = new FastScanner();
        StringBuilder sb = new StringBuilder();

        int n = fs.nextInt();
        int[][] best = new int[101][101];
        int[] sum = new int[101];

        for (int i = 0; i < n; i++) {
            int u = fs.nextInt();
            int p = fs.nextInt();
            int sc = fs.nextInt();

            // 只在刷新题目最高分时更新总分。
            if (sc > best[u][p]) {
                sum[u] += sc - best[u][p];
                best[u][p] = sc;
            }

            int rank = 1;
            for (int j = 1; j <= 100; j++) {
                if (sum[j] > sum[1]) {
                    rank++;
                }
            }
            sb.append(rank).append('\n');
        }

        System.out.print(sb.toString());
    }
}

3. 双门控加权器

问题描述

现在给定一组单头注意力的输入矩阵 ,需要按照下面的 TG-SA 规则计算注意力矩阵 和最终输出矩阵

设:

  • 阈值参数固定为

先计算原始打分矩阵:

对第 行,记:

然后做双门控映射:

之后对每一行执行两步操作:

  1. 只保留该行数值最大的 个位置,其余位置全部置为 。 如果出现边界并列,优先保留下标更小的位置。
  2. 若这一行和为 ,则把整行改成均匀分布;否则将整行归一化,使这一行元素和为

归一化后的矩阵记为 ,最终输出矩阵为:

你需要输出矩阵 和矩阵

输入格式

输入是一行 JSON 对象,包含以下字段:

  • "Q":一个 的二维数组;
  • "K":一个 的二维数组;
  • "V":一个 的二维数组;
  • "k_top":一个整数。

保证三组矩阵的行数相同,且列数相同。

输出格式

输出一个 JSON 对象,格式为:

{"A":[...],"O":[...]}

其中:

  • A 的注意力矩阵;
  • O 的输出矩阵。

你的实数答案只要满足绝对误差或相对误差不超过 ,就会被判定为正确。

样例输入 1

{"Q":[[1,0],[0,1]],"K":[[1,0],[0,1]],"V":[[1,2],[3,4]],"k_top":1}

样例输出 1

{"A":[[1.0,0.0],[0.0,1.0]],"O":[[1.0,2.0],[3.0,4.0]]}

数据范围

  • 输入矩阵元素的绝对值不超过
  • 阈值参数固定为
样例 解释说明
样例1 都是单位矩阵时,两行都会把注意力集中到自己对应的位置上,因此 也是单位矩阵,最终

题解

这题本质上就是一次按规则改造过的注意力计算,流程完全固定。

第一步:计算原始打分矩阵

直接按照定义做矩阵乘法:

第二步:逐行做双门控映射

对每一行先求最大值

  • ,这一行门控后的结果直接全是
  • 否则就按题目给的分段函数,把每个值映射到区间

第三步:Top-k 稀疏化

每一行只保留最大的 个位置,其余清零。

为了让答案唯一,需要处理并列边界。这里按题意规定:若数值相同,则优先保留下标更小的位置。

第四步:行归一化

  • 如果一整行都是 ,就改成均匀分布。
  • 否则把整行除以它的行和,得到注意力矩阵

第五步:计算输出矩阵

最后再做一次矩阵乘法:

总时间复杂度是 ,空间复杂度是

参考代码

  • Python
import json
import math
import sys

input = lambda: sys.stdin.readline().strip()


def normalize_num(x):
    s = f"{x:.10f}".rstrip("0").rstrip(".")
    if "." not in s:
        s += ".0"
    return float(s)


def solve():
    raw = sys.stdin.read().strip()
    data = json.loads(raw)

    q = data["Q"]
    k = data["K"]
    v = data["V"]
    k_top = data["k_top"]

    l = len(q)
    d = len(q[0])
    inv = 1.0 / math.sqrt(d)

    score = [[0.0] * l for _ in range(l)]
    for i in range(l):
        for j in range(l):
            cur = 0.0
            for t in range(d):
                cur += q[i][t] * k[j][t]
            score[i][j] = cur * inv

    att = [[0.0] * l for _ in range(l)]
    for i in range(l):
        s_max = max(score[i])
        row = [0.0] * l

        if s_max > 0:
            lo = 0.2 * s_max
            hi = 0.6 * s_max
            for j in range(l):
                val = score[i][j]
                if val <= lo:
                    row[j] = 0.0
                elif val <= hi:
                    row[j] = (val - lo) / (hi - lo)
                else:
                    row[j] = 1.0

        order = list(range(l))
        order.sort(key=lambda idx: (-row[idx], idx))
        keep = set(order[:k_top])
        for j in range(l):
            if j not in keep:
                row[j] = 0.0

        sm = sum(row)
        if sm == 0:
            row = [1.0 / l] * l
        else:
            row = [x / sm for x in row]

        att[i] = row

    out = [[0.0] * d for _ in range(l)]
    for i in range(l):
        for j in range(l):
            if att[i][j] == 0:
                continue
            for t in range(d):
                out[i][t] += att[i][j] * v[j][t]

    ans = {
        "A": [[normalize_num(x) for x in row] for row in att],
        "O": [[normalize_num(x) for x in row] for row in out],
    }
    print(json.dumps(ans, separators=(",", ":")))


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

void skip_spaces(const string &s, int &p) {
    while (p < (int)s.size() && isspace((unsigned char)s[p])) {
        p++;
    }
}

vector<vector<double>> parse_2d(const string &s, int &p) {
    skip_spaces(s, p);
    vector<vector<double>> res;
    if (p >= (int)s.size() || s[p] != '[') {
        return res;
    }

    p++;
    skip_spaces(s, p);

    while (p < (int)s.size() && s[p] == '[') {
        p++;
        skip_spaces(s, p);

        vector<double> row;
        while (p < (int)s.size()) {
            int st = p;
            if (s[p] == '+' || s[p] == '-') {
                p++;
            }
            while (p < (int)s.size() &&
                   (isdigit((unsigned char)s[p]) || s[p] == '.' || s[p] == 'e' ||
                    s[p] == 'E' || s[p] == '+' || s[p] == '-')) {
                p++;
            }
            row.push_back(strtod(s.c_str() + st, nullptr));
            skip_spaces(s, p);
            if (p < (int)s.size() && s[p] == ',') {
                p++;
                skip_spaces(s, p);
            } else if (p < (int)s.size() && s[p] == ']') {
                p++;
                break;
            } else {
                break;
            }
        }

        res.push_back(row);
        skip_spaces(s, p);
        if (p < (int)s.size() && s[p] == ',') {
            p++;
            skip_spaces(s, p);
        } else {
            break;
        }
    }

    if (p < (int)s.size() && s[p] == ']') {
        p++;
    }
    return res;
}

int parse_int_after_key(const string &s, const string &key) {
    int p = s.find(key);
    p = s.find(':', p);
    p++;
    skip_spaces(s, p);
    int sign = 1;
    if (s[p] == '-') {
        sign = -1;
        p++;
    }
    int val = 0;
    while (p < (int)s.size() && isdigit((unsigned char)s[p])) {
        val = val * 10 + (s[p] - '0');
        p++;
    }
    return sign * val;
}

string format_num(double x) {
    ostringstream out;
    out << fixed << setprecision(10) << x;
    string s = out.str();
    while (!s.empty() && s.back() == '0') {
        s.pop_back();
    }
    if (!s.empty() && s.back() == '.') {
        s.push_back('0');
    }
    return s;
}

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

    string raw, line;
    while (getline(cin, line)) {
        raw += line;
    }

    int p = raw.find("\"Q\"");
    p = raw.find('[', p);
    vector<vector<double>> q = parse_2d(raw, p);

    p = raw.find("\"K\"");
    p = raw.find('[', p);
    vector<vector<double>> k = parse_2d(raw, p);

    p = raw.find("\"V\"");
    p = raw.find('[', p);
    vector<vector<double>> v = parse_2d(raw, p);

    int k_top = parse_int_after_key(raw, "\"k_top\"");

    int l = (int)q.size();
    int d = (int)q[0].size();
    double inv = 1.0 / sqrt((double)d);

    vector<vector<double>> score(l, vector<double>(l, 0.0));
    for (int i = 0; i < l; i++) {
        for (int j = 0; j < l; j++) {
            double cur = 0;
            for (int t = 0; t < d; t++) {
                cur += q[i][t] * k[j][t];
            }
            score[i][j] = cur * inv;
        }
    }

    vector<vector<double>> att(l, vector<double>(l, 0.0));
    for (int i = 0; i < l; i++) {
        double s_max = *max_element(score[i].begin(), score[i].end());
        vector<double> row(l, 0.0);

        if (s_max > 0) {
            double lo = 0.2 * s_max;
            double hi = 0.6 * s_max;
            for (int j = 0; j < l; j++) {
                double val = score[i][j];
                if (val <= lo) {
                    row[j] = 0.0;
                } else if (val <= hi) {
                    row[j] = (val - lo) / (hi - lo);
                } else {
                    row[j] = 1.0;
                }
            }
        }

        vector<int> ord(l);
        iota(ord.begin(), ord.end(), 0);
        sort(ord.begin(), ord.end(), [&](int x, in

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

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

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

全部评论

相关推荐

03-30 00:09
吉林大学 C++
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

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