oppo杯手机diy大赛
oppo杯手机diy大赛
OPPO 笔试真题 · 2024 秋招 · 研发岗
判题请到牛客原题提交。
题目描述
小欧参加了“oppo杯”手机diy大赛,该大赛将从外观和性能两个方面综合评比每个选手的作品。
共有个评委,每个评委为小欧的作品的外观和性能分别打了分数。
为了分数更加有效,每个指标的分数都将分别去除最高分和最低分之后计算剩下分数的平均数即为指标的分数,然后求出这两个指标分数的平均数,即为最终的分数。
然而,突发紧急公告,有一个评委可能由于徇私舞弊失去评价资格。
小欧想知道,如果是第个评委失去资格,她最终能获得多少分?你需要回答的答案。
输入格式
第一行输入一个正整数,代表评委的数量。
接下来的行,每行输入两个正整数和,代表该评委给小欧作品的外观分数和性能分数。
输出格式
输出行,第行输出一个浮点数,代表第个评委失去资格后,小欧最终的分数。
如果你输出的答案精度和标准答案的相对误差不超过,则认为答案正确。
示例
示例 1
输入:
4
1 1
2 3
3 2
4 1输出:
2.5
2
1.5
2说明:
第一个评委失去资格后,小欧的作品的外观分数为3(去掉最高分4和最低分2),性能分数是2,最终的分数是2.5。
后三个评委失去资格的情况不再赘述。
解题思路
第一步:理解问题本质
题目里有两套分数:外观分 和性能分 。
每个指标各自去掉一个最高分和一个最低分,把剩下的数求平均得到一个指标分,
再把「外观分」和「性能分」这两个数字取平均,才是最终得分。
题目要对 各回答一次:如果第 个评委失去资格,最终得分是多少。
失去一个评委后还剩 个评委(题目保证 ,所以至少还剩 3 个),
在这 个分数里再去掉最高、最低,剩下 个求平均。
第二步:暴力解法以及它为什么不行
最直白的写法是:对每个 ,把「剩下的 个数」重新收集一遍,
排序后去掉首尾,求和。
单次是 ,一共 次,总复杂度 。
时大约是 次操作,必然超时。
第三步:把「总和」和「极值」拆开
换个角度:我们真正需要的只是三个量。
- 该指标的总和 (固定不变,预处理一次);
- 去掉第 个评委后,剩下那 个数里的最大值 ;
- 同理的最小值 。
那么去掉三个人(第 个 + 最高 + 最低)之后,剩下的和就是
一共 个数,所以这个指标的平均值是 。
第四步: 求出「去掉 之后的最大值 / 最小值」
把该指标的所有分数排序,得到 ordered。设全局最大值是 hi、最小值是 lo。
- 最大值:如果 就是
hi,而且hi只出现一次,那么去掉它之后,
剩下的最大值降为次大值ordered[-2];只要hi出现两次以上,
去掉一个之后最大值仍然是hi。 - 最小值:同理, 是唯一的最小值时最小值升为
ordered[1],
否则仍是lo。
于是每个 只需要常数次比较和四则运算。
最后合并两个指标:
值得一提的是分子是两个整数之和,只在最后做一次除法,浮点误差最小。
完整代码实现
import sys
def residual_sums(vals):
"""返回数组 res:res[i] 表示去掉第 i 个数、以及剩下那些数里的最大值和最小值之后的元素和
n >= 4,所以去掉一个数后还剩至少 3 个,最大值和最小值总是两个不同的位置。
"""
n = len(vals)
total = sum(vals)
ordered = sorted(vals)
lo, hi = ordered[0], ordered[-1]
# 全局最小 / 最大值各出现了多少次
cnt_lo = 0
while cnt_lo < n and ordered[cnt_lo] == lo:
cnt_lo += 1
cnt_hi = 0
while cnt_hi < n and ordered[n - 1 - cnt_hi] == hi:
cnt_hi += 1
res = [0] * n
for i, v in enumerate(vals):
# 去掉 v 之后,剩下的数里的最大值 / 最小值
mx = ordered[-2] if (v == hi and cnt_hi == 1) else hi
mn = ordered[1] if (v == lo and cnt_lo == 1) else lo
res[i] = total - v - mx - mn
return res
def fmt(x):
"""把浮点数输出成不带多余尾零的写法(2.5 / 2 / 1.5)"""
s = "%.12f" % x
if "." in s:
s = s.rstrip("0").rstrip(".")
return s
def solve():
"""主求解函数:对每个评委算出「去掉他之后」的两个指标平均值,再取平均"""
data = sys.stdin.buffer.read().split()
if not data:
return
n = int(data[0])
appearance = [0] * n
performance = [0] * n
p = 1
for i in range(n):
appearance[i] = int(data[p])
performance[i] = int(data[p + 1])
p += 2
rest_a = residual_sums(appearance)
rest_b = residual_sums(performance)
# 每个指标剩下 n-3 个数,两个指标再取平均 -> 除以 2*(n-3)
denom = 2 * (n - 3)
out = [fmt((rest_a[i] + rest_b[i]) / denom) for i in range(n)]
sys.stdout.write("\n".join(out) + "\n")
if __name__ == "__main__":
solve()示例推演
样例数据:,四个评委的分数是 。
逐个评委失去资格:
| 失去资格的评委 | 剩下的外观分 | 去极值后 | 剩下的性能分 | 去极值后 | 最终得分 |
|---|---|---|---|---|---|
| 1 号 | 2, 3, 4 | 3 | 3, 2, 1 | 2 | |
| 2 号 | 1, 3, 4 | 3 | 1, 2, 1 | 1 | |
| 3 号 | 1, 2, 4 | 2 | 1, 3, 1 | 1 | |
| 4 号 | 1, 2, 3 | 2 | 1, 3, 2 | 2 |
对应输出:
2.5
2
1.5
2再用「 求极值」的写法核对一次 1 号评委的情况:
- 外观分 ,全局最大值 4(只出现一次)、全局最小值 1(只出现一次)。
去掉 1 号评委的 1 之后,最小值升为次小值 2,最大值仍是 4,
所以剩下的和是 ,除以 得 3。 - 性能分 ,全局最大值 3(唯一),全局最小值 1(出现了两次)。
去掉 1 号评委的 1 之后,最小值仍是 1(4 号评委也是 1),最大值仍是 3,
剩下的和是 ,得 2。
两者平均正好是 2.5,与表格一致。
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 暴力:每个 重新排序 | 时超时 | ||
| 排序 + 查询 | 推荐,瓶颈只在一次排序 |
易错点总结
1. 「去掉最高最低」是对每个指标分别做的
外观分去掉的是外观分的最高最低,性能分去掉的是性能分的最高最低。
它们可能来自不同的评委,不要「去掉同一个评委的两个分数」。
2. 去掉第 个之后,极值可能会变
这是最容易漏的一处: 恰好是唯一的最大值时,最大值要退到次大值;
但只要有重复的最大值,去掉一个之后最大值不变。
判断依据是「值是否等于极值」 + 「极值的出现次数是否为 1」。
3. 分母是 ,不是 或
去掉 1 个评委后剩 个分数,再去掉最高、最低两个,剩 个。
两个指标再取平均,总分母是 。
4. 分子先加再除,别写两次除法
和 分别算好再加,会多一次舍入;
先算 这个整数,最后除一次 精度更好。
5. 求和会超过 32 位整型
、,总和可达 ,超过 int 的范围。
Python 的整数不溢出,但用 C++ 写时要用 long long。
扩展思考
- 如果规则改成「去掉 个最高分和 个最低分」,排序后用前缀和,
同样可以在 时间里算出每个 的答案。 - 如果两个指标不是「各自去极值」而是「按总分去极值」,那 与 就绑在一起了,
需要按 排序,处理方式会不一样。 - 这类「删掉一个元素后统计量如何变化」的题目有一个共同套路:
先把与删除无关的部分预处理成 可查的表(总和、次大值、次小值),
再枚举被删除的元素。