2026-03-15 某电商厂暑期笔试复盘

Published 2026-03-15 04:00 3162 words 16 min read Handwritten Every word here was typed by me.

Zhiheng Wang avatar

Zhiheng Wang

技术文章、个人随笔、项目与作品体验记录

No posts in this series
This post is not yet available in English. Showing the original.
2026-03-15 某电商厂暑期笔试复盘 题目一:主播排序 题目大意 给定 N 个直播片段,第 i 个片段包含: :主播编号 :指标 A,越大越好 :指标 B,越大越好 :时间,越早越好 :输入顺序,越早越好 排序规则依次比较:A 降序 → B 降序 → T 升序 → index 升序。 要求: 每个主播只保留其最优的一个片段; 用这些最优片段对主播排名; 有 Q 次询问,每次给一个片段编号...

2026-03-15 某电商厂暑期笔试复盘

题目一:主播排序

题目大意

给定 N 个直播片段,第 i 个片段包含:

  • uid:主播编号
  • A:指标 A,越大越好
  • B:指标 B,越大越好
  • T:时间,越早越好
  • index:输入顺序,越早越好

排序规则依次比较:A 降序 → B 降序 → T 升序 → index 升序。

要求:

  1. 每个主播只保留其最优的一个片段;
  2. 用这些最优片段对主播排名;
  3. 有 Q 次询问,每次给一个片段编号 x:若 x 是该主播的最优片段,输出该主播的排名;否则输出 0。

思路分析

分组取最优,再用代表元素排序。

第一步遍历所有片段,对每个 uid 维护当前最优片段,比较规则就是题给的排序规则。

第二步把这些最优片段拿出来,按同一套规则排序,得到主播排名。

第三步处理询问:先判 x 是不是它所属主播的最优片段(直接与 best[uid] 记的编号比),不是就输出 0;是就输出这个主播的排名。

实现上有个小技巧:把 T、index 取负后塞进元组,四个维度就都统一成”越大越好”,直接按元组降序比较即可,不用手写比较函数,而且”选代表”和”排主播”两步可以复用同一套比较。因为 index 唯一,任意两个片段的键都不会相等,排名不存在并列。

复杂度 O(Nlog⁡N+Q)O(N \log N + Q)。

代码

import sys


def main():
    data = sys.stdin.buffer.read().split()
    p = 0
    n = int(data[p]); p += 1

    keys = [None] * (n + 1)     # 片段编号 -> 排序键
    uid_of = [0] * (n + 1)      # 片段编号 -> 主播编号
    best = {}                   # 主播编号 -> (排序键, 片段编号)

    for seg in range(1, n + 1):
        uid = int(data[p]); A = int(data[p + 1])
        B = int(data[p + 2]); T = int(data[p + 3])
        p += 4

        # A 大、B 大、T 小、index 小 -> 全部转成"越大越好"
        key = (A, B, -T, -seg)
        keys[seg] = key
        uid_of[seg] = uid
        if uid not in best or key > best[uid][0]:
            best[uid] = (key, seg)

    # 第二步:用每个主播的最优片段排名
    order = sorted(best.values(), key=lambda kv: kv[0], reverse=True)
    rank = {}
    for r, (_, seg) in enumerate(order, start=1):
        rank[uid_of[seg]] = r

    # 第三步:回答询问
    q = int(data[p]); p += 1
    out = []
    for _ in range(q):
        x = int(data[p]); p += 1
        uid = uid_of[x]
        if best[uid][1] != x:
            out.append("0")
        else:
            out.append(str(rank[uid]))

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


main()

题目二:电动车充电最小费用

题目大意

一辆电动车要行驶总距离 L,电池容量 C(满电最多跑 C 公里),每行驶 1 公里消耗 1 单位电量。

路上有若干充电站,第 i 个位于位置 xix_i,电价为每单位 pip_i。可以在任意站点买电,但电量不能超过 C。

求从起点到终点的最小花费;无法到达输出 -1。

题面没写出发时的电量,下面代码按”起点当作价格 0 的站点”处理,等价于满电出发。若原题是空箱出发、起点不能加油,把起点从站点列表里去掉、fuel 从 0 开始,其余逻辑不变。

思路分析

贪心。站在站点 i 往前看 C 公里:

  • 如果范围内存在价格更低的站 j:只买到刚好能到 j 的电,到 j 时电量正好为 0。因为 j 的电更便宜,在 i 多买是亏的。
  • 如果范围内没有更便宜的站:在 i 加满。因为接下来 C 公里内都比 i 贵,能囤就囤。

这里容易写错的一个点:要找的是范围内第一个更便宜的站,不是范围内最便宜的站 —— 因为一旦开到第一个更便宜的站,决策就在那里重新开始,后面的价格由那时的判断接管。

无解判断也简单:把起点、终点和所有站点按位置排序,任意相邻两点的距离超过 C,就有一段跑不过去,输出 -1。

单次决策要向前扫描可达范围内的站点,整体 O(n⋅可达站数)O(n \cdot \text{可达站数})。

代码

import sys


def main():
    data = sys.stdin.buffer.read().split()
    p = 0
    L = int(data[p]); C = int(data[p + 1]); n = int(data[p + 2]); p += 3

    st = []
    for _ in range(n):
        x = int(data[p]); pr = int(data[p + 1]); p += 2
        st.append((x, pr))
    st.sort()

    pos = [0] + [x for x, _ in st] + [L]
    price = [0] + [pr for _, pr in st] + [0]   # 起点按价格 0(满电出发),终点只是占位

    # 无解:任意相邻两点间距超过续航
    for i in range(1, len(pos)):
        if pos[i] - pos[i - 1] > C:
            print(-1)
            return

    fuel = 0
    cost = 0
    for i in range(len(pos) - 1):
        # 找可达范围内第一个比当前站便宜的站
        j = i + 1
        while j < len(pos) and pos[j] - pos[i] <= C and price[j] >= price[i]:
            j += 1

        if j < len(pos) and pos[j] - pos[i] <= C:
            target = pos[j] - pos[i]        # 买到刚好到 j
        else:
            target = C                       # 没有更便宜的,加满

        if fuel < target:
            cost += (target - fuel) * price[i]
            fuel = target

        fuel -= pos[i + 1] - pos[i]          # 开到下一站

    print(cost)


main()

题目三:删除子串到达目标坐标

题目大意

给定长度为 N 的字符串 S,字符取自 U、D、L、R,分别表示二维平面上的上下左右移动。从 (0,0) 出发,按字符串依次移动。

允许执行一次操作:删除一个连续子串(也可以不删,也可以删除整个字符串)。

给定目标坐标 (tx,ty)(tx, ty),判断能否通过这样的删除使终点落在目标上。下面代码按”求最短删除长度,无解输出 -1”写;若原题只问可行性,把返回值当 bool 用即可。

思路分析

我的思路

考场上的做法是:先算出所有前缀和,用”目标坐标减总位移”得到要找的子串位移;然后扫前缀和,用字典记每个坐标最后出现的下标,查”当前坐标减 target”是否在字典里,命中就更新最短长度。

关于顺序,我当时是先存当前点、再查 当前点 - target。这没有问题:只有当 target 是 (0,0)(0,0) 时才会匹配到自己,而那种情况答案本来就是 0。

推荐解法

设整串位移为 SS,目标为 TT,被删子串位移为 subsub。删除后剩下部分的位移就是 S−subS - sub,要求它等于 TT:

S−sub=T⟹sub=S−TS - sub = T \quad\Longrightarrow\quad sub = S - T

于是问题变成:是否存在一个连续子串,其位移恰好是 S−TS - T;要最短删除长度,就取满足条件的子串里最短的那个。

用前缀和做:pre[i]pre[i] 表示前 i 个字符的位移,子串 (l,i](l, i] 的位移为 pre[i]−pre[l]pre[i] - pre[l]。条件是

pre[i]−pre[l]=S−T⟺pre[l]=pre[i]−(S−T)pre[i] - pre[l] = S - T \quad\Longleftrightarrow\quad pre[l] = pre[i] - (S - T)

所以扫到 i 时,只要在哈希表里查坐标 pre[i]−(S−T)pre[i] - (S - T) 出现过没有;为了长度最短,表里存的应该是该坐标最后一次出现的下标。查完再把 pre[i]pre[i] 写回表里。

两个边界:若 S=TS = T,不删就已经到目标,答案是 0;若始终没命中,输出 -1。

整题 O(N)O(N)。我那份还原版和这个推荐版的差别只有两处:needneed 的方向写成了 T−ST - S,以及查表、存表的先后顺序。具体是哪一处让考场代码只过了部分数据,没有留下原始代码,这里不下结论。

代码

我的版本

按上面的思路还原,保留”方向用目标减总位移”和”先存再查”两个特征:

def shortest_delete(S, tx, ty):
    n = len(S)
    dx = {'U': 0, 'D': 0, 'L': -1, 'R': 1}
    dy = {'U': 1, 'D': -1, 'L': 0, 'R': 0}

    pre = [(0, 0)] * (n + 1)
    for i, ch in enumerate(S):
        pre[i + 1] = (pre[i][0] + dx[ch], pre[i][1] + dy[ch])

    total = pre[n]
    # 我当时写的方向:目标减总位移
    need = (tx - total[0], ty - total[1])

    last = {}          # 坐标 -> 最后一次出现的下标
    best = n + 1
    for i in range(n + 1):
        cur = pre[i]
        last[cur] = i                                   # 先存
        look = (cur[0] - need[0], cur[1] - need[1])
        if look in last:                                # 再查
            best = min(best, i - last[look])

    return best if best <= n else -1

推荐版本

def shortest_delete(S, tx, ty):
    n = len(S)
    dx = {'U': 0, 'D': 0, 'L': -1, 'R': 1}
    dy = {'U': 1, 'D': -1, 'L': 0, 'R': 0}

    pre = [(0, 0)] * (n + 1)
    for i, ch in enumerate(S):
        pre[i + 1] = (pre[i][0] + dx[ch], pre[i][1] + dy[ch])

    total = pre[n]
    if total == (tx, ty):
        return 0

    need = (total[0] - tx, total[1] - ty)      # 被删子串应有的位移 S - T

    ans = n + 1
    last = {}                                  # 坐标 -> 最后一次出现的下标
    for i in range(n + 1):
        cur = pre[i]
        want = (cur[0] - need[0], cur[1] - need[1])
        if want in last:                       # 先查,保证 l < i
            ans = min(ans, i - last[want])
        last[cur] = i                          # 再存

    return ans if ans <= n else -1

题目四:资源包容量最小化

题目大意

给定两个长度为 N 的数组:

  • A[i]:第 i 个时段的峰值请求
  • B[i]:第 i 个时段的原有容量

系统最多可以使用 M 个资源包。每个资源包的容量都是 x,持续 W 个时段;如果在第 i 个时段开启,则覆盖区间 [i,i+W−1][i, i+W-1],超出序列长度也没关系。

要求所有时段满足 B[i] + 覆盖到该时段的资源包容量之和 ≥\ge A[i]。

求最小的资源包容量 x,以及在 x 最小时使用的资源包数量;无解输出 -1。

思路分析

单调性很明显:x 越大越容易满足,x 可行则任何更大的 x 也可行。所以二分 x,配一个线性判定 check(x)。

判定过程从左到右扫:维护当前生效的总贡献 cur 和已用包数 used。若 B[i] + cur < A[i],缺口是 need = A[i] - B[i] - cur,一个包能补 x,就需要新开 ⌈need/x⌉\lceil need / x \rceil 个。新开的包覆盖 [i,i+W−1][i, i+W-1]。

包的开启位置取 ii 本身(能给当前缺口补量的最晚位置就是 i),越往后扫越该在最晚位置开,这样覆盖的时段不会浪费。

我的思路

二分框架我认出来了,判断函数按最直白的方式实现:开一个长度 N 的数组 add 记每个时段已生效的贡献,需要新开 k 个包时,就把 k⋅xk \cdot x 加到 i∼i+W−1i \sim i+W-1 的每一个位置上。写起来最直观,代价是每次判定 O(N⋅W)O(N \cdot W),总复杂度 O(N⋅W⋅log⁡V)O(N \cdot W \cdot \log V)。

推荐解法

同样是二分,把贡献从”逐位加”换成差分数组维护:

  • 新开包时,cur 当场加上 k⋅xk \cdot x(本时段立刻生效);
  • 只在位置 i + W 记一笔 −k⋅x-k \cdot x,表示这批包到时失效;i + W 越界就不记(题目说过界不影响)。

这样每次判定只扫一遍,O(N)O(N),总复杂度 O(Nlog⁡V)O(N \log V)。

几个容易卡住的细节:

  • 新开包后 cur 必须立刻加上 gain,否则下个时段仍用旧的 cur 判断,会重复开包;
  • 向上取整写 (need + x - 1) // x;
  • 若所有 A[i] <= B[i],答案是 0 个包、容量 0;否则二分下界从 1 开始;
  • 题目还要求输出 x 最小时的包数,所以 check 直接返回 used,超过 M 时返回 None。

代码

两版共用下面这个主函数:

def solve():
    hi = max((a - b for a, b in zip(A, B)), default=0)
    if hi <= 0:
        return 0, 0                      # 本来就不用资源包

    lo, ans, best_used = 1, None, None
    while lo <= hi:
        mid = (lo + hi) // 2
        used = check(mid)
        if used is not None:
            ans, best_used = mid, used
            hi = mid - 1                 # 可行,试着更小
        else:
            lo = mid + 1                 # 不可行,必须更大

    return (ans, best_used) if ans is not None else -1

我的版本

def check(x):
    n = len(A)
    add = [0] * n        # 每个时段已生效的资源包贡献
    used = 0
    for i in range(n):
        if B[i] + add[i] < A[i]:
            need = A[i] - B[i] - add[i]
            k = (need + x - 1) // x
            used += k
            if used > M:
                return None
            for t in range(i, min(n, i + W)):    # 逐个时段加贡献,O(W)
                add[t] += k * x
    return used

推荐版本

def check(x):
    n = len(A)
    diff = [0] * (n + 1)      # 差分:在 i 处生效,在 i+W 处失效
    cur = 0                   # 当前生效的总贡献
    used = 0
    for i in range(n):
        cur += diff[i]
        if B[i] + cur < A[i]:
            need = A[i] - B[i] - cur
            k = (need + x - 1) // x
            used += k
            if used > M:
                return None
            gain = k * x
            cur += gain                   # 本时段立刻生效
            if i + W < n:
                diff[i + W] -= gain       # 到 i+W 失效
    return used

Written by Handwritten

Random Posts

© 2026 Zhiheng Wang @traceonsnow
Powered by theme astro-koharu · Inspired by Shoka
Loading...