洛谷算法1-2 排序(NOIP经典真题解析)java(持续更新)

技术笔记:算法1-2 排序(NOIP经典真题解析)

排序是算法竞赛中最基础、最核心的技能之一,它不仅是许多复杂算法的基础,也常常作为独立考点出现在各类编程竞赛中。本文将通过三道NOIP经典真题(选票统计、奖学金评选、欢乐的跳跃),讲解不同排序场景下的解题思路和代码实现技巧,帮助你掌握排序算法的实际应用。

一、 选票统计(大规模数据排序)

题目核心要求

给定n名候选人的m张选票(1 ≤ n ≤ 999,1 ≤ m ≤ 2,000,000),将所有选票按候选人编号从小到大排序并输出。

解题思路

这道题的核心挑战在于处理大规模输入数据(m最大为2e6),需要从时间和空间两方面进行优化:

  1. 输入优化 :使用BufferedReader替代Scanner,因为其读取速度更快,能避免因输入过大导致的超时(TLE)。
  2. 排序优化 :直接使用Java内置的Arrays.sort(),它基于经过高度优化的双枢轴快速排序算法,性能远超手写的冒泡或选择排序。
  3. 输出优化 :使用StringBuilder拼接结果,最后一次性输出,减少频繁I/O操作带来的性能损耗。

完整Java代码

java 复制代码
import java.util.Arrays;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        // 使用 BufferedReader 处理大规模输入,速度更快
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        // 读取第一行,获取候选人数量n和选票数量m
        String[] firstLine = br.readLine().split(" ");
        int n = Integer.parseInt(firstLine[0]);
        int m = Integer.parseInt(firstLine[1]);

        // 读取第二行,获取所有选票数据
        String[] voteStrs = br.readLine().split(" ");
        int[] votes = new int[m];
        for (int i = 0; i < m; i++) {
            votes[i] = Integer.parseInt(voteStrs[i]);
        }

        // 使用内置排序,简单高效
        Arrays.sort(votes);

        // 使用 StringBuilder 拼接结果,减少I/O次数
        StringBuilder sb = new StringBuilder();
        for (int vote : votes) {
            sb.append(vote).append(" ");
        }

        // 输出结果,trim()去除末尾多余空格
        System.out.println(sb.toString().trim());

        // 关闭资源
        br.close();
    }
}

关键注意点

  1. 输入速度是关键 :对于百万级别的输入,Scanner的速度瓶颈会非常明显,BufferedReader是更优选择。
  2. 内置排序的优势Arrays.sort()在处理基本类型数组时效率极高,除非有特殊需求,否则不要手写排序算法。
  3. 减少I/O操作 :频繁调用System.out.println()会严重拖慢程序速度,使用StringBuilder可以将多次输出合并为一次。

二、 奖学金评选(多关键字排序)

题目核心要求

给定n名学生的语文、数学、英语成绩,按以下规则评选出前5名并输出其学号和总分:

  1. 优先按总分从高到低排序
  2. 若总分相同,按语文成绩从高到低排序
  3. 若语文成绩也相同,按学号从小到大排序

解题思路

这是一个典型的多关键字排序问题,需要自定义排序规则。

  1. 数据封装 :创建一个Student类,封装学生的学号、各科成绩和总分,便于统一管理和排序。
  2. 自定义排序 :实现冒泡排序(或使用Arrays.sort()结合自定义比较器),严格按照题目给定的优先级进行比较和交换。
  3. 输出前5名:排序完成后,直接输出数组中前5个学生的学号和总分。

完整Java代码

java 复制代码
import java.util.Scanner;

class Student {
    int id;
    int chinese;
    int math;
    int english;
    int total;
}

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        Student[] students = new Student[n];

        // 读取并初始化学生信息
        for (int i = 0; i < n; i++) {
            students[i] = new Student();
            students[i].id = i + 1; // 学号从1开始
            students[i].chinese = sc.nextInt();
            students[i].math = sc.nextInt();
            students[i].english = sc.nextInt();
            students[i].total = students[i].chinese + students[i].math + students[i].english;
        }

        // 冒泡排序实现多关键字排序
        for (int i = 0; i < n - 1; i++) {
            for (int j = 0; j < n - i - 1; j++) {
                if (shouldSwap(students[j], students[j + 1])) {
                    Student temp = students[j];
                    students[j] = students[j + 1];
                    students[j + 1] = temp;
                }
            }
        }

        // 输出前5名学生
        for (int i = 0; i < 5; i++) {
            System.out.println(students[i].id + " " + students[i].total);
        }

        sc.close();
    }

    /**
     * 判断两个学生是否需要交换位置
     * @param a 前一个学生
     * @param b 后一个学生
     * @return 需要交换返回true,否则返回false
     */
    private static boolean shouldSwap(Student a, Student b) {
        if (a.total != b.total) {
            return a.total < b.total; // 总分低的在后,需要交换
        } else if (a.chinese != b.chinese) {
            return a.chinese < b.chinese; // 语文成绩低的在后,需要交换
        } else {
            return a.id > b.id; // 学号大的在后,需要交换
        }
    }
}

关键注意点

  1. 多关键字比较的优先级:必须严格按照题目给定的顺序进行比较,先比较总分,再比较语文成绩,最后比较学号。
  2. 冒泡排序的稳定性:冒泡排序是稳定排序,能够保证当关键字相同时,原始顺序得以保留,这在处理学号这种次要关键字时非常重要。
  3. 代码的可维护性 :将比较逻辑抽离到shouldSwap方法中,使代码结构更清晰,也更容易理解和维护。

三、 欢乐的跳跃(排序辅助验证)

题目核心要求

给定一个包含n个元素的整数数组,判断其是否符合"欢乐的跳跃"定义:数组中任意两个连续元素之差的绝对值,必须包含[1, n-1]之间的所有整数。

解题思路

这道题的巧妙之处在于,我们可以利用排序来辅助验证:

  1. 计算差值 :遍历数组,计算每对连续元素之差的绝对值,得到一个包含n-1个元素的差值数组。
  2. 排序差值数组:对差值数组进行排序。
  3. 验证连续性 :检查排序后的差值数组是否恰好是1, 2, 3, ..., n-1。如果是,则符合"欢乐的跳跃";否则不符合。

完整Java代码

java 复制代码
import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        // 循环处理多组输入
        while (scanner.hasNext()) {
            int n = scanner.nextInt();
            int[] a = new int[n];
            for (int i = 0; i < n; i++) {
                a[i] = scanner.nextInt();
            }

            // 处理n=1的特殊情况,直接符合条件
            if (n == 1) {
                System.out.println("Jolly");
                continue;
            }

            // 计算连续元素差值的绝对值
            int[] diffs = new int[n - 1];
            for (int i = 0; i < n - 1; i++) {
                diffs[i] = Math.abs(a[i + 1] - a[i]);
            }

            // 对差值数组进行排序
            Arrays.sort(diffs);

            // 验证差值是否连续
            boolean isJolly = true;
            for (int i = 0; i < n - 1; i++) {
                if (diffs[i] != i + 1) {
                    isJolly = false;
                    break;
                }
            }

            // 输出结果
            System.out.println(isJolly ? "Jolly" : "Not jolly");
        }
        scanner.close();
    }
}

关键注意点

  1. 特殊情况处理 :当n=1时,数组没有连续元素,默认符合"欢乐的跳跃"定义。
  2. 排序的辅助作用:排序将问题转化为一个简单的线性验证,使我们能够高效地判断差值的连续性。
  3. 时间复杂度分析 :算法的时间复杂度主要由排序步骤决定,为O(n log n),这在题目给定的n ≤ 1000的约束下是完全可行的。

相关推荐
ShineWinsu20 小时前
对于C++:继承的解析—上
开发语言·数据结构·c++·算法·面试·笔试·继承
pp起床20 小时前
动态规划 | part05
算法·动态规划
小付同学呀20 小时前
C语言学习(五)——输入/输出
c语言·开发语言·学习
GuangHeAI_ATing20 小时前
国密算法SSD怎么选?这3款国产固态硬盘安全又高速
算法
码农阿豪20 小时前
Nacos 日志与 Raft 数据清理指南:如何安全释放磁盘空间
java·安全·nacos
梦幻精灵_cq21 小时前
学C之路:不可或缺的main()主函数框架(Learn-C 1st)
c语言·开发语言
直有两条腿21 小时前
【大模型】Langchain4j
java·langchain
love530love21 小时前
Scoop 完整迁移指南:从 C 盘到 D 盘的无缝切换
java·服务器·前端·人工智能·windows·scoop
雨泪丶21 小时前
代码随想录算法训练营-Day34
算法
消失的旧时光-194321 小时前
C++ 多线程与并发系统取向(二)—— 资源保护:std::mutex 与 RAII(类比 Java synchronized)
java·开发语言·c++·并发