大厂真题 / 携程

携程 5.10 笔试真题 - 研发岗

本场考试概述

考试时间:2026年5月10日 考试岗位:研发岗 难度评级:中等偏难

考点分析

  1. 第一题:数学不变量(难度简单)
  2. 第二题:位运算 + 按位贡献分析(难度中等)
  3. 第三题:贪心构造(难度中等)
  4. 第四题:树状数组 + 冒泡轨迹分析(难度困难)

建议策略

  1. 第一、二题为基础思维题,优先稳拿
  2. 第三题贪心结构清晰,掌握”独立计数”性质即可 AC
  3. 第四题需要发现冒泡排序中元素右移的轨迹规律,时间不够可保前三题

第一题:资源平衡

题目描述

两个王国分别拥有 $x$ 和 $y$ 单位水晶。它们可以进行”平衡仪式”:拥有至少 2 单位水晶的王国消耗 2 单位自己的水晶,为另一个王国增加 1 单位水晶。仪式可由任一王国发起、进行任意次。问能否最终使双方水晶数量相等。

样例

输入

4
8 5
5 8
6 6
7 5

输出

YES
YES
YES
NO

思路分析

第一步:分析操作对差值的影响

设 $A$ 国发起仪式:$(x, y) \to (x-2, y+1)$,差值 $x - y$ 变化 $-3$。$B$ 国发起时差值变化 $+3$。无论谁发起,差值只能变化 $\pm 3$。

第二步:得出不变量

$(x - y) \mod 3$ 是操作的不变量。要让 $x = y$(即差值为 0),必须初始差值能被 3 整除。

第三步:充分性验证

若 $(x - y) \equiv 0 \pmod{3}$,设差为 $3k$。让拥有更多水晶的一方发起 $k$ 次仪式即可让两者相等(每次操作前发起方至少有 2 单位水晶可以验证)。

题解代码

import sys

input = sys.stdin.readline

T = int(input())
for _ in range(T):
    x, y = map(int, input().split())
    print("YES" if (x - y) % 3 == 0 else "NO")

复杂度分析

时间复杂度:$O(T)$ 空间复杂度:$O(1)$


第二题:删除

题目描述

给定长度为 $n$ 的正整数数组 $a$,记所有数按位与的结果为 $\text{AND}$。统计有多少个下标 $i$,满足删除 $a_i$ 后剩余元素的按位与严格变大

样例

输入

3
5
7 3 7 7 7
3
1 1 1
2
2 1

输出

1
0
2

思路分析

第一步:理解”按位与变大”的含义

按位与的性质:删除元素只会让某些位从 0 变成 1(因为少了一个 0 的贡献),不会让 1 变成 0。所以”严格变大”等价于:存在某一位,删除后从 0 翻成了 1。

第二步:充要条件

某一位从 0 变成 1,说明原本全数组中该位为 0 的元素恰好只有 $a_i$ 一个。删掉它后,该位在剩余所有元素中都是 1,按位与结果该位变 1,整体变大。

第三步:逐位统计

对每一位 $b$(0 到 29),扫一遍数组统计该位为 0 的元素个数。若恰好只有 1 个,把对应下标标记为”可删除”。最终统计被标记的下标数量(同一个下标可能因多个位被标记,用布尔数组自然去重)。

题解代码

import sys

input = sys.stdin.readline

T = int(input())
for _ in range(T):
    n = int(input())
    a = list(map(int, input().split()))
    mark = [False] * n
    for b in range(30):
        cnt = 0
        idx = -1
        for i in range(n):
            if not ((a[i] >> b) & 1):
                cnt += 1
                idx = i
                if cnt > 1:
                    break
        if cnt == 1:
            mark[idx] = True
    print(sum(mark))

复杂度分析

时间复杂度:$O(30n)$ 空间复杂度:$O(n)$


第三题:寿司

题目描述

有一个长度为 $n$、仅由小写字母组成的字符串 $s$(已丢失),以及一个保留下来的数组 $a$。其中 $a_i$ 表示在位置 $i$ 之前,与 $s_i$ 相同的字符个数。请根据数组 $a$ 重构任意一个满足条件的字符串;若不存在则输出 -1

样例

输入

2
7
0 0 1 0 2 1 3
3
0 0 2

输出

abacaba
-1

思路分析

第一步:理解约束

$a_i$ 的含义是”在 $s$ 的前 $i$ 个字符中,和 $s_i$ 相同的字符恰好出现了 $a_i$ 次”。换句话说,$s_i$ 这个字母在位置 $i$ 之前已经出现过 $a_i$ 次。

第二步:贪心构造

从左到右扫描,维护 26 个字母各自当前的出现次数 $\text{cnt}[c]$。对位置 $i$,找任意一个满足 $\text{cnt}[c] = a_i$ 的字母填入,然后 $\text{cnt}[c]$ 加 1。

第三步:正确性

字母之间的计数相互独立。选了某个字母不会影响其他字母的计数,因此任选一个满足条件的字母都不会让后续位置变得无解。若 26 个字母都不满足则无解。

题解代码

import sys

input = sys.stdin.readline

T = int(input())
for _ in range(T):
    n = int(input())
    a = list(map(int, input().split()))
    cnt = [0] * 26
    result = []
    valid = True
    for x in a:
        found = -1
        for c in range(26):
            if cnt[c] == x:
                found = c
                break
        if found == -1:
            valid = False
            break
        result.append(chr(ord('a') + found))
        cnt[found] += 1
    print(''.join(result) if valid else '-1')

复杂度分析

时间复杂度:$O(26n)$ 空间复杂度:$O(n)$


第四题:单数组交换

题目描述

给定长度为 $n$ 的数组 $a$ 和数组 $b$。对 $a$ 执行标准冒泡排序(将其排为非递减)。每次实际发生交换时,若交换位置为 $j$(交换 $a_j$ 和 $a_{j+1}$),代价为:若 $b_j < b_{j+1}$ 则代价 1,否则代价 0。求冒泡排序完成后所有交换的总代价。

样例

输入

2
4
2 1 2 1
3 1 2 2
6
17 15 12 10 18 3
1 5 1 3 1 2

输出

1
7

思路分析

第一步:直接模拟的瓶颈

标准冒泡排序是 $O(n^2)$,$n \leq 10^5$ 时无法承受。需要分析冒泡过程中每次交换落在哪些边界上。

第二步:分析元素的运动轨迹

定义两个量:

  • $L_i$:元素 $a_i$ 在排序过程中被”左推”的次数(左侧有多少个比它大的元素会从它身上跨过)= 左侧严格大于 $a_i$ 的元素个数
  • $R_i$:元素 $a_i$ “右移”的次数(右侧有多少个比它小的元素需要它越过)= 右侧严格小于 $a_i$ 的元素个数

第三步:右移横跨的边界区间

冒泡排序中,元素 $a_i$ 先被左推 $L_i$ 格到位置 $i - L_i$,然后连续右移 $R_i$ 格。第 $k$ 次右移跨过边界 $i - L_i + k - 1$。因此元素 $a_i$ 的所有右移横跨边界区间为 $[i - L_i, \ i - L_i + R_i - 1]$。

第四步:前缀和加速

定义 $\text{cum}[p] = \sum_{q=0}^{p-1} [b_q < b_{q+1}]$(边界处代价为 1 的前缀和)。元素 $a_i$ 贡献的总代价为 $\text{cum}[i - L_i + R_i] - \text{cum}[i - L_i]$。

第五步:用树状数组求 $L_i$ 和 $R_i$

  • 从左到右扫描,用 BIT 求”已插入元素中严格大于 $a_i$ 的个数”得到 $L_i$
  • 从右到左扫描,用 BIT 求”已插入元素中严格小于 $a_i$ 的个数”得到 $R_i$

题解代码

import sys

input = sys.stdin.readline

def solve():
    n = int(input())
    a = list(map(int, input().split()))
    b = list(map(int, input().split()))
    if n <= 1:
        print(0)
        return

    # 离散化
    sa = sorted(set(a))
    rank_map = {v: i + 1 for i, v in enumerate(sa)}
    rk = [rank_map[x] for x in a]
    m = len(sa)

    # 树状数组
    bit = [0] * (m + 2)
    def update(i):
        while i <= m:
            bit[i] += 1
            i += i & -i
    def query(i):
        s = 0
        while i > 0:
            s += bit[i]
            i -= i & -i
        return s

    # 从左到右求 L[i]: 已插入中严格大于 a[i] 的个数
    L = [0] * n
    for i in range(n):
        L[i] = query(m) - query(rk[i])
        update(rk[i])

    # 从右到左求 R[i]: 已插入中严格小于 a[i] 的个数
    bit = [0] * (m + 2)
    R = [0] * n
    for i in range(n - 1, -1, -1):
        R[i] = query(rk[i] - 1)
        update(rk[i])

    # 前缀和: cum[p] = [0, p) 内 b[q] < b[q+1] 的边界数
    cum = [0] * (n + 1)
    for q in range(n - 1):
        cum[q + 1] = cum[q] + (1 if b[q] < b[q + 1] else 0)

    # 对每个元素累加贡献
    cost = 0
    for i in range(n):
        if R[i] == 0:
            continue
        s = i - L[i]
        f = s + R[i]
        cost += cum[f] - cum[s]

    print(cost)

T = int(input())
for _ in range(T):
    solve()

复杂度分析

时间复杂度:$O(n \log n)$,两次树状数组扫描 空间复杂度:$O(n)$


小结

  • 第一题是纯数学题,找到差值模 3 的不变量即可一行判断
  • 第二题按位分析贡献,统计每一位上为 0 的元素是否唯一
  • 第三题贪心构造字符串,利用字母计数独立性保证任选合法字母都不破坏后续可行性
  • 第四题是本场硬题,需要深入分析冒泡排序中元素的运动轨迹,用树状数组 + 前缀和将 $O(n^2)$ 降到 $O(n \log n)$