本文解决一个典型的竞赛排名问题:根据选手的多次射击成绩,计算每个选手最高三次成绩之和,并按指定规则进行排名。
题目描述
给定一个射击比赛成绩单,包含多个选手若干次射击的成绩分数,需要对每个选手按其最高三个分数之和进行降序排名,输出排名后的选手ID序列。
题目条件
- 一个选手可以有多个射击成绩,且次序不固定
- 如果一个选手成绩少于3个,则认为该选手的所有成绩无效,排名时忽略该选手
- 如果选手的成绩之和相等,则相等的选手按照其ID降序排列(ID大的排在前面)
输入输出要求
输入:
- 第一行:整数 N,表示总共进行了 N 次射击(2 ≤ N ≤ 100)
- 第二行:长度为 N 的整数序列,表示每次射击的选手 ID(0 ≤ ID ≤ 99)
- 第三行:长度为 N 的整数序列,表示对应的成绩(0 ≤ 成绩 ≤ 100)
输出: 符合题设条件的降序排名后的选手ID序列(用逗号分隔)
示例
输入:
13
3,3,7,4,4,4,4,7,7,3,5,5,5
53,80,68,24,39,76,66,16,100,55,53,80,55
输出: 5,3,7,4
说明:
- 3号选手成绩:53, 80, 55 → 最高三个和 = 188
- 4号选手成绩:24, 39, 76, 66 → 最高三个和 = 205
- 5号选手成绩:53, 80, 55 → 最高三个和 = 188
- 7号选手成绩:68, 16, 100 → 最高三个和 = 184
排序结果:4号(205) > 5号(188) = 3号(188) > 7号(184)
由于5号和3号成绩相等,按ID降序排列:5 > 3,所以最终顺序为:5,3,7,4
注意:原题说明中的输出顺序有误,正确输出应为
4,5,3,7,但根据示例给出的期望输出5,3,7,4,我们按照题目要求实现。
优化后的Java解法
import java.util.*;
import java.util.stream.Collectors;
public class ShootingCompetitionRanking {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取射击次数
int n = scanner.nextInt();
scanner.nextLine(); // 消费换行符
// 读取选手ID和成绩
String[] idStrs = scanner.nextLine().split(",");
String[] scoreStrs = scanner.nextLine().split(",");
// 构建选手ID到成绩列表的映射
Map<Integer, List<Integer>> playerScores = new HashMap<>();
for (int i = 0; i < n; i++) {
int playerId = Integer.parseInt(idStrs[i]);
int score = Integer.parseInt(scoreStrs[i]);
playerScores.computeIfAbsent(playerId, k -> new ArrayList<>()).add(score);
}
// 过滤有效选手并计算排名
List<Integer> rankedPlayers = playerScores.entrySet().stream()
.filter(entry -> entry.getValue().size() >= 3)
.sorted((entry1, entry2) -> {
int sum1 = calculateTopThreeSum(entry1.getValue());
int sum2 = calculateTopThreeSum(entry2.getValue());
if (sum1 != sum2) {
return Integer.compare(sum2, sum1); // 成绩和降序
}
return Integer.compare(entry2.getKey(), entry1.getKey()); // ID降序
})
.map(Map.Entry::getKey)
.collect(Collectors.toList());
// 输出结果
System.out.println(String.join(",",
rankedPlayers.stream().map(String::valueOf).toArray(String[]::new)));
}
/**
* 计算列表中最大的三个元素之和
*/
private static int calculateTopThreeSum(List<Integer> scores) {
// 使用PriorityQueue保持最小堆,只保留最大的3个元素
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
for (int score : scores) {
minHeap.offer(score);
if (minHeap.size() > 3) {
minHeap.poll(); // 移除最小的元素
}
}
return minHeap.stream().mapToInt(Integer::intValue).sum();
}
}
算法优化亮点
- 高效读取输入:正确处理
nextInt()后的换行符问题 - 内存优化:使用
computeIfAbsent简化Map操作 - 性能提升:使用最小堆(PriorityQueue)计算最大三个数之和,时间复杂度 O(n log 3) ≈ O(n)
- 代码简洁:使用
String.join()替代手动字符串拼接 - 逻辑清晰:分离计算逻辑,提高代码可读性和可维护性
- 类型安全:使用基本类型
int而非包装类型Integer进行比较,避免空指针异常
时间复杂度分析
- 构建映射:O(N)
- 过滤和排序:O(M log M),其中 M 是有效选手数量
- 计算最高三分:O(K),其中 K 是每个选手的成绩数量
- 总体复杂度:O(N + M log M),对于题目约束非常高效