目录

裴先生
裴先生
发布于 2022-04-18 / 2 阅读
0
0

算法题:统计射击比赛成绩

原创

本文解决一个典型的竞赛排名问题:根据选手的多次射击成绩,计算每个选手最高三次成绩之和,并按指定规则进行排名。

题目描述

给定一个射击比赛成绩单,包含多个选手若干次射击的成绩分数,需要对每个选手按其最高三个分数之和进行降序排名,输出排名后的选手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();
    }
}

算法优化亮点

  1. 高效读取输入:正确处理 nextInt() 后的换行符问题
  2. 内存优化:使用 computeIfAbsent 简化Map操作
  3. 性能提升:使用最小堆(PriorityQueue)计算最大三个数之和,时间复杂度 O(n log 3) ≈ O(n)
  4. 代码简洁:使用 String.join() 替代手动字符串拼接
  5. 逻辑清晰:分离计算逻辑,提高代码可读性和可维护性
  6. 类型安全:使用基本类型 int 而非包装类型 Integer 进行比较,避免空指针异常

时间复杂度分析

  • 构建映射:O(N)
  • 过滤和排序:O(M log M),其中 M 是有效选手数量
  • 计算最高三分:O(K),其中 K 是每个选手的成绩数量
  • 总体复杂度:O(N + M log M),对于题目约束非常高效

原创

版权声明:本博客原创文章,由 裴先生 2022年04月18日 发表。
转载说明:除特殊说明外本站文章皆由 CC BY-NC-SA 4.0 协议发布,转载须注明出处。


评论