大厂真题 / 携程

携程 5.21 笔试真题 - 算法岗

本场考试概述

考试时间:2026年5月21日 考试岗位:算法岗 难度评级:中等

考点分析

  1. 第一题:模拟(难度简单)
  2. 第二题:三维网格枚举(难度中等)
  3. 第三题:sklearn GridSearchCV + StandardScaler(难度中等)
  4. 第四题:中途相遇法 + 哈希计数(难度中等偏难)

建议策略

  • 第一题模拟题秒杀,注意”严格大于”对应 > 而非 >=,64 位整数累加
  • 第二题立方体方向枚举要数清 26 个方向 + 合法起点的笛卡儿积,初值用负无穷保险
  • 第三题熟悉 sklearn 管线:StandardScaler 只在训练集 fit、StratifiedKFold 保比例、GridSearchCV 字典序选优
  • 第四题异或四元组的关键是把暴力按”中途相遇”拆成两组各 $O(n^2)$,配滑动分界点保证下标约束

第一题:拿石子

题目描述

有 $n$ 堆石子。你只做一次统计:对每一堆,如果这堆石子的数量严格大于 $x$,你就从这堆里拿走 $y$ 颗石子;否则这堆不拿。请计算你一共会拿走多少颗石子。

输入描述

一行输入三个整数 $n, x, y$。

接下来一行输入 $n$ 个整数 $a_1, a_2, \ldots, a_n$,表示第 $i$ 堆的石子数量。

输出描述

输出一个整数,表示总共拿走的石子数量。

样例

输入

5 3 2
1 3 4 5 3

输出

4

样例解释:共有 5 堆,阈值为 3,每次从满足条件的堆拿 2 颗。第 3 堆数量 4 > 3 拿走 2 颗,第 4 堆数量 5 > 3 拿走 2 颗,其余三堆数量不严格大于 3 都不拿,共拿走 4 颗。

思路分析

第一步:理解条件。对每堆判断数量是否严格大于阈值 $x$,满足条件的堆各贡献 $y$ 颗石子,求总贡献。各堆独立,单次线性扫描即可。

第二步:计数累乘。扫描数组,统计满足 $a_i > x$ 的堆数 $\text{cnt}$,答案为 $\text{cnt} \times y$。

第三步:注意边界。题面强调”严格大于”,代码用 > 而非 >=,等于 $x$ 不计。Python 整数自动大数,无需关心位宽溢出。

题解代码

import sys

data = sys.stdin.buffer.read().split()
n = int(data[0])
x = int(data[1])
y = int(data[2])
a = list(map(int, data[3:3 + n]))
# 严格大于阈值才拿 y 颗(等于 x 不算)
ans = sum(y for v in a if v > x)
print(ans)

复杂度分析

时间复杂度:$O(n)$,单次线性扫描。 空间复杂度:$O(n)$,存储数组(可边读边累计降为 $O(1)$)。


第二题:立方体最大权值直线

题目描述

给出一个边长为 $n$ 的立方体网格。网格中每个小方块对应一个整数权值。你需要从立方体中选择一条长度为 $n$ 的直线。

允许选择的直线方向包括:沿坐标轴方向(分别与 x、y 或 z 轴平行的整行/整列/整柱);沿任一坐标平面的面对角线方向(例如在 xy 平面中,每一步 x 与 y 同时加减、z 不变;其余两个平面同理);沿空间对角线方向(每一步 x、y、z 同时加减)。

形式化地,设方向步长为 $(dx, dy, dz)$,其中各分量取自 ${-1, 0, 1}$ 且不全为零。选择方向后,起点 $(x_0, y_0, z_0)$ 需要满足:对所有 $k \in [0, n-1]$,都有 $0 \le x_0 + k \cdot dx < n$(y、z 同理),从而得到一条恰好经过 $n$ 个小方块的直线。请计算在所有允许方向与所有合法起点中,经过的小方块权值之和的最大值。

输入描述

在一行上输入一个整数 $n$,表示立方体的边长。

此后按”层”的顺序给出权值。对于第 $k$ 个”层”,输入 $n$ 行:第 $i$ 行输入 $n$ 个整数,表示第 $k$ 层第 $i$ 行第 $j$ 列小方块的权值。

输出描述

输出一个整数,表示所有允许方向与所有合法起点中,经过的小方块权值之和的最大值。

样例

输入

2
1 2
3 4
5 6
7 8

输出

15

样例解释:立方体边长 2。第 0 层的两行为 [1,2] 和 [3,4];第 1 层的两行为 [5,6] 和 [7,8]。枚举所有方向所有合法起点,沿空间对角方向 (1,1,1) 从 (0,0,0) 出发经过格子 1 和 8,权值和为 9;从 (0,1,1) 沿 (1,-1,-1) 经过 4 和 5,和为 9。最大的直线经过 7 和 8,权值和为 15。

思路分析

第一步:方向枚举。每维取自 ${-1, 0, 1}$,三维联合后扣除全零方向共 $3^3 - 1 = 26$ 个有效方向。

第二步:合法起点判定。直线上所有 $n$ 个格子坐标都必须落在 $[0, n-1]$ 内。对 $x$ 这一维独立看:

  • $dx = 0$:起点 $x_0$ 可取 $[0, n-1]$
  • $dx = +1$:$x_0$ 必须为 0(否则末端 $x_0 + (n-1)$ 越界)
  • $dx = -1$:$x_0$ 必须为 $n-1$(否则末端 $x_0 - (n-1)$ 越界)

三个维度合法集合做笛卡儿积,即得该方向的全部合法起点。

第三步:沿线累加取全局最大值。固定方向与起点后,沿直线累加 $n$ 个格子权值,更新全局最大值。权值可负,初值必须用负无穷而非 0,否则全负权值的直线会被屏蔽。

第四步:复杂度估算。方向数 26 为常数,起点数 $O(n^2)$,每条直线累加 $O(n)$,总复杂度 $O(n^3)$。

题解代码

import sys


def solve():
    data = sys.stdin.buffer.read().split()
    n = int(data[0])

    # 输入按 (k, i, j) 顺序给出,重排成 a[i][j][k]
    flat = list(map(int, data[1:1 + n ** 3]))
    a = [[[0] * n for _ in range(n)] for _ in range(n)]
    idx = 0
    for k in range(n):
        for i in range(n):
            for j in range(n):
                a[i][j][k] = flat[idx]
                idx += 1

    # 初值用 -inf:权值可负,全负直线不能被 0 屏蔽
    ans = -10 ** 18

    # 三层循环枚举方向 (dx, dy, dz),每维 in {-1, 0, 1},共 27 种
    for dx in (-1, 0, 1):
        for dy in (-1, 0, 1):
            for dz in (-1, 0, 1):
                # 跳过全零方向,剩 26 个有效方向
                if dx == 0 and dy == 0 and dz == 0:
                    continue
                # 合法起点范围:
                # d=0 -> 起点在该维可任取 [0, n-1]
                # d=+1 -> 起点必为 0(否则末端越界右侧)
                # d=-1 -> 起点必为 n-1(否则末端越界左侧)
                xs = range(n) if dx == 0 else ([0] if dx == 1 else [n - 1])
                ys = range(n) if dy == 0 else ([0] if dy == 1 else [n - 1])
                zs = range(n) if dz == 0 else ([0] if dz == 1 else [n - 1])
                # 遍历该方向全部合法起点
                for x0 in xs:
                    for y0 in ys:
                        for z0 in zs:
                            # 沿方向 k 步累加,k 从 0 到 n-1 共 n 个格子
                            s = 0
                            for k in range(n):
                                s += a[x0 + k * dx][y0 + k * dy][z0 + k * dz]
                            if s > ans:
                                ans = s
    print(ans)


solve()

复杂度分析

时间复杂度:$O(n^3)$。方向数 26 为常数,起点数 $O(n^2)$,每条直线累加 $O(n)$。 空间复杂度:$O(n^3)$,存储立方体权值。


第三题:SVM网格搜索

题目描述

请在仅依赖 numpy / pandas / scikit-learn 的前提下,实现一个线性支持向量机(SVM)分类器,并通过 Stratified 3-Fold 交叉验证 + 网格搜索自动选择最佳惩罚系数 $C$。完成后对给定测试样本输出类别预测。

1. 读取数据

  • 训练集:二维列表,最后一列为标签(0/1),前 $m$ 列为数值特征。
  • 测试集:二维列表,仅包含特征(与训练集同维度)。

2. 建模管线

  • 预处理:StandardScaler(仅在训练集 fit,再 transform)。
  • 基础模型使用 SVC,参数固定值:kernel='linear'random_state=42
  • 网格:C in [0.1, 1.0, 10.0]
  • Stratified 3-Fold 交叉验证参数固定值:n_splits=3shuffle=Truerandom_state=42
  • 评估指标:accuracy
  • 使用 GridSearchCV 组合以上要素;当多组参数获得相同最高分时,GridSearchCV 会按参数字典序选择最先出现的那组(即较小的 $C$ 值)。

3. 模型训练与预测

在完整训练集上用最佳参数 $C$ 重新训练;对测试集直接预测得到标签序列。

4. 结果输出

仅输出测试部分的预测标签(0 / 1)。

输入描述

第一行输入两个整数 $n, m$,分别表示训练样本数和特征维度。

接下来 $n$ 行,每行包含 $m+1$ 个浮点数,前 $m$ 个为特征,最后一个为整数标签(0/1)。

接下来一行输入一个整数 $q$,表示测试样本数。

接下来 $q$ 行,每行包含 $m$ 个浮点数,表示一个测试样本的特征。

输出描述

输出 $q$ 行,每行一个整数(0 或 1),表示对应测试样本的预测标签。

样例

输入

20 2
-1.20 -0.40 0
-0.80 -1.10 0
-1.30 -0.90 0
-0.70 -0.60 0
-1.50 -0.30 0
-0.90 -1.40 0
-0.50 -0.80 0
-1.10 -0.20 0
-0.60 -1.30 0
-1.40 -0.70 0
1.20 0.40 1
0.80 1.10 1
1.30 0.90 1
0.70 0.60 1
1.50 0.30 1
0.90 1.40 1
0.50 0.80 1
1.10 0.20 1
0.60 1.30 1
1.40 0.70 1
3
1.00 1.00
-1.00 -1.00
0.50 0.50

输出

1
0
1

样例解释:训练集共 20 个样本,前 10 个标签为 0 分布在第三象限,后 10 个标签为 1 分布在第一象限。经 GridSearchCV 在 [0.1, 1.0, 10.0] 中网格搜索后选出最佳 $C$,对测试集预测:第 1 个样本落在正类区域得到 1;第 2 个样本落在负类区域得到 0;第 3 个样本距离决策超平面靠近正类侧得到 1。

思路分析

第一步:特征标准化。将每维特征减均值除标准差,使各维同尺度,避免某些维度因数值大而主导决策面。仅由训练集统计均值和标准差,再 transform 测试集。若先 fit 全集再切分,测试集统计量会泄漏进训练流程,导致评估偏乐观。

第二步:线性 SVC 目标函数。在标准化特征空间求最大间隔超平面,软间隔 SVM 优化目标中超参 $C$ 平衡间隔大小与分类错误:$C$ 越大越倾向贴近训练数据,越小越倾向大间隔。

第三步:Stratified K-Fold 评分。将训练集按标签比例切成 3 折。对每个候选 $C$,依次留 1 折当验证、另 2 折当训练,得到 3 个验证准确率取均值。每折保持正负样本比例与原始一致,避免随机切分导致某折正样本过少。参数 n_splits=3, shuffle=True, random_state=42

第四步:网格搜索选优。在候选集 [0.1, 1.0, 10.0] 中取分数最高者。GridSearchCV 按候选列表顺序遍历,平分时保留首个出现的候选(即较小的 $C$ 值)。候选必须用有序结构(list),不能用 set,否则遍历顺序未定义。

第五步:refit 全集训练。选定 $C$ 后用完整训练集重新拟合(refit=True 默认行为),最终模型对测试集预测得到标签序列。

题解代码

import sys
import numpy as np
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import GridSearchCV, StratifiedKFold


def solve():
    data = sys.stdin.read().split()
    idx = 0

    # 第一行:训练样本数 n、特征维度 m
    n, m = int(data[idx]), int(data[idx + 1])
    idx += 2

    # 训练集 n 行,前 m 列为特征、最后 1 列为标签 (0 / 1)
    train = []
    for _ in range(n):
        row = [float(v) for v in data[idx:idx + m + 1]]
        train.append(row)
        idx += m + 1

    # 测试样本数 q
    q = int(data[idx])
    idx += 1

    # 测试集 q 行,每行 m 个特征(无标签,待预测)
    test = []
    for _ in range(q):
        row = [float(v) for v in data[idx:idx + m]]
        test.append(row)
        idx += m

    # 拆分特征与标签
    train = np.array(train)
    X_train = train[:, :m]
    y_train = train[:, m].astype(int)
    X_test = np.array(test)

    # 标准化:仅用训练集统计 mu, sigma,避免测试集信息泄漏
    scaler = StandardScaler()
    X_train_scaled = scaler.fit_transform(X_train)
    X_test_scaled = scaler.transform(X_test)

    # Stratified 3-Fold:每折保持原始正负样本比例
    skf = StratifiedKFold(n_splits=3, shuffle=True, random_state=42)

    # 网格候选:list 有序,平分时 GridSearchCV 保留首个(较小的 C)
    grid = {'C': [0.1, 1.0, 10.0]}

    # 线性核 SVC
    svc = SVC(kernel='linear', random_state=42)

    # GridSearchCV:3 个 C x 3 折 = 9 次训练
    # refit=True(默认):选好 C 后用完整训练集再训一次
    gs = GridSearchCV(svc, grid, cv=skf, scoring='accuracy', refit=True)
    gs.fit(X_train_scaled, y_train)

    # 用 refit 后的最终模型预测测试集
    preds = gs.predict(X_test_scaled)
    print('\n'.join(str(int(p)) for p in preds))


solve()

复杂度分析

时间复杂度:$O(n^2 m)$,共 9 次 SVC 训练,线性核单次 $O(n^2 m)$,规模下毫秒级完成。 空间复杂度:$O(nm)$,存训练与测试矩阵。


第四题:异或四元组计数

题目描述

给定一个长度为 $n$ 的整数数组 $a$ 与整数 $k$,请统计满足 $a_i \oplus a_j \oplus a_p \oplus a_q = k$ 的四元组 $(i, j, p, q)$ 数量。为避免重复计数,约定四个下标两两不同且按 $i < j < p < q$ 计数。

名词解释

  • 按位异或(xor,记作 $\oplus$):对每一位二进制独立计算,相同为 0,不同为 1。
  • 两两不同:四个下标互不相同,不可复用同一位置的元素;本题按 $i < j < p < q$ 唯一计数。

输入描述

每个测试文件均包含多组测试数据。第一行输入一个整数 $T$ 代表数据组数,每组测试数据描述如下:

第一行输入两个整数 $n, k$。

第二行输入 $n$ 个整数 $a_1, a_2, \ldots, a_n$。

所有测试中 $\sum n \le 2000$。

输出描述

输出 $T$ 行,第 $i$ 行输出第 $i$ 组数据的答案(满足条件的四元组数量)。

样例

输入

2
4 0
1 2 1 2
5 2
1 1 2 2 3

输出

1
2

样例解释:第一组数据 $n=4$,$k=0$,数组为 [1,2,1,2]。唯一可选四元组下标 (0,1,2,3),异或和 $1 \oplus 2 \oplus 1 \oplus 2 = 0$,满足条件,答案为 1。第二组数据 $n=5$,$k=2$,数组为 [1,1,2,2,3]。满足条件的四元组下标为 (0,1,2,4) 和 (0,1,3,4),共 2 组。

思路分析

第一步:暴力分析。朴素做法枚举所有四元组验证,复杂度 $O(n^4)$,单组 $n = 2000$ 直接超时。

第二步:中途相遇拆分。利用异或消去律,把等式 $a_i \oplus a_j \oplus a_p \oplus a_q = k$ 改写为 $a_i \oplus a_j = k \oplus a_p \oplus a_q$。左侧只依赖 $(i, j)$ 对、右侧只依赖 $(p, q)$ 对。用哈希表把左右匹配,整体复杂度从 $O(n^4)$ 降为 $O(n^2)$。

第三步:下标约束处理。如果把全部 $n^2$ 对一次入表,可能会把同一下标同时当左侧和右侧使用,违反 $i < j < p < q$ 约束。解决办法是用滑动分界点:按 $p$ 从 2 推进到 $n-2$,时刻维护”分界点左侧”的哈希表。

第四步:滑动分界点实现

  • 每当 $p$ 推进一步,新增 $j = p-1$ 配对所有 $i < p-1$ 的对,将 $a_i \oplus a_j$ 入表(共 $p-1$ 个键),保证这些 $(i,j)$ 对的下标都严格小于 $p$。
  • 右侧查询:对当前 $p$,枚举 $q > p$,所需的左侧异或值为 $k \oplus a_p \oplus a_q$,查哈希表累加贡献。

这样保证了 $i < j < p < q$ 的下标严格递增约束。

题解代码

import sys


def solve():
    data = sys.stdin.buffer.read().split()
    idx = 0
    T = int(data[idx]); idx += 1
    out = []

    for _ in range(T):
        n = int(data[idx]); k = int(data[idx + 1]); idx += 2
        a = list(map(int, data[idx:idx + n])); idx += n

        # cnt[v]:分界点 p 左侧已加入的 (i, j) 对中 a_i ^ a_j = v 的对数
        cnt = {}
        ans = 0

        # 滑动分界点 p:作为四元组第三个下标,从 2 到 n-2
        for p in range(2, n - 1):
            # 推进 cnt:新增 j = p-1 配对所有 i < p-1 的对
            jn = p - 1
            for i in range(jn):
                v = a[i] ^ a[jn]
                cnt[v] = cnt.get(v, 0) + 1

            # 右侧查询:对每个 q > p,所需左侧异或值 t = k ^ a[p] ^ a[q]
            base = k ^ a[p]
            for q in range(p + 1, n):
                t = base ^ a[q]
                if t in cnt:
                    ans += cnt[t]

        out.append(str(ans))

    print('\n'.join(out))


solve()

复杂度分析

时间复杂度:单组 $O(n^2)$。入表共 $O(n^2)$ 次写,查询共 $O(n^2)$ 次读。跨组合计 $O((\sum n)^2)$ 在 $\sum n \le 2000$ 下约 $4 \times 10^6$ 次操作。 空间复杂度:$O(n^2)$,哈希表最坏存所有 $(i,j)$ 对的异或值。


小结

  • 第一题是纯模拟签到题,统计满足 $a_i > x$ 的堆数乘以 $y$ 即可,注意严格大于
  • 第二题三维网格枚举的核心是 26 个方向 + 合法起点的笛卡儿积,初值用负无穷避免全负权值被屏蔽
  • 第三题是 sklearn 管线题,严格按题目参数搭建 StandardScaler + SVC + StratifiedKFold + GridSearchCV 管线即可
  • 第四题利用异或消去律将 $O(n^4)$ 暴力拆成”中途相遇”的 $O(n^2)$,滑动分界点巧妙保证下标严格递增约束