1. 项目概述:天梯赛L2-009抢红包题目解析
这道来自天梯赛的L2级别编程题,考察的是对Java集合框架和排序算法的综合运用能力。题目模拟了一个多人抢红包的场景,要求统计每位参与者的收入金额并按照特定规则排序输出。作为PAT考试中的经典题型,它完美融合了实际业务场景和基础算法考察点。
在实际开发中,类似的需求非常常见——比如电商平台的销售排行榜、社交软件的步数统计,或是游戏中的玩家积分榜。这类问题的核心在于正确处理数据收集、聚合计算和结果展示三个环节。通过这道题,我们可以掌握Java中面向对象设计、集合操作和比较器使用的实战技巧。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心需求与解题思路
2.1 题目要求拆解
题目给出N个人参与的K次抢红包记录,每条记录包含:红包编号、抢到红包的人的编号、抢到的金额。需要最终输出:
- 每个人最终的收入金额(收到金额-发出金额)
- 按收入降序排列,收入相同则按抢到红包次数降序,仍相同则按编号升序
输入格式示例:
code复制10 // 总人数N
3 // 红包个数K
3 2 10 // 第1个红包:编号3,2个人抢,总金额10元
2 1 // 第1个抢到的人:编号2,抢到1元
3 9 // 第2个抢到的人:编号3,抢到9元
2.2 数据结构设计
面对这种需要频繁按ID查询和更新的场景,HashMap是最佳选择。我们可以设计一个Person类来封装用户信息:
java复制class Person {
int id;
double money; // 净收入
int count; // 抢到红包次数
// 构造函数、getter/setter省略
}
使用Map<Integer, Person>来存储所有用户信息,键为用户ID,值为对应的Person对象。这种设计既保证了O(1)时间复杂度的查询效率,又保持了数据的封装性。
2.3 算法流程设计
- 初始化阶段:创建N个Person对象,初始化id为1~N,money和count为0
- 处理每个红包:
- 读取红包发出者的ID和总金额
- 从发出者的money中扣除总金额
- 处理每个抢到红包的记录,更新对应人员的money和count
- 结果处理:
- 将Map中的Person对象转为List
- 使用自定义Comparator进行多条件排序
- 格式化输出结果
3. 关键实现细节
3.1 输入处理技巧
天梯赛的输入数据量通常较大,使用Scanner可能会超时。推荐使用BufferedReader:
java复制BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] firstLine = br.readLine().split(" ");
int N = Integer.parseInt(firstLine[0]);
int K = Integer.parseInt(firstLine[1]);
对于每行数据的解析,注意处理可能的空白字符:
java复制String[] parts = line.trim().split("\\s+"); // 处理多个空格分隔的情况
3.2 金额计算的精度处理
浮点数计算可能存在精度问题,建议将所有金额转换为分(整数)来处理:
java复制// 将元转换为分
int totalFen = (int)(totalMoney * 100);
// 分配时也按分计算
int perFen = totalFen / numPeople;
最后输出时再转换回元:
java复制System.out.printf("%d %.2f", person.id, person.money / 100.0);
3.3 多条件排序实现
Java中实现多条件排序有两种方式:
- 实现Comparable接口:
java复制class Person implements Comparable<Person> {
@Override
public int compareTo(Person o) {
if(this.money != o.money) {
return Double.compare(o.money, this.money); // 收入降序
}
if(this.count != o.count) {
return o.count - this.count; // 次数降序
}
return this.id - o.id; // ID升序
}
}
- 使用Comparator链(Java 8+):
java复制Comparator<Person> comparator = Comparator
.comparing(Person::getMoney, Comparator.reverseOrder())
.thenComparing(Person::getCount, Comparator.reverseOrder())
.thenComparing(Person::getId);
4. 完整代码实现
java复制import java.io.*;
import java.util.*;
class Person {
int id;
double money;
int count;
Person(int id) {
this.id = id;
this.money = 0;
this.count = 0;
}
}
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] firstLine = br.readLine().split(" ");
int N = Integer.parseInt(firstLine[0]);
int K = Integer.parseInt(firstLine[1]);
Map<Integer, Person> people = new HashMap<>();
for(int i = 1; i <= N; i++) {
people.put(i, new Person(i));
}
for(int i = 0; i < K; i++) {
String[] parts = br.readLine().split(" ");
int sender = Integer.parseInt(parts[0]);
int num = Integer.parseInt(parts[1]);
double total = Double.parseDouble(parts[2]);
// 发红包的人扣钱
people.get(sender).money -= total;
// 分配红包
List<int[]> receivers = new ArrayList<>();
double remaining = total;
for(int j = 0; j < num - 1; j++) {
parts = br.readLine().split(" ");
int receiver = Integer.parseInt(parts[0]);
double amount = Double.parseDouble(parts[1]);
people.get(receiver).money += amount;
people.get(receiver).count++;
remaining -= amount;
}
// 最后一个人拿剩余金额
parts = br.readLine().split(" ");
int lastReceiver = Integer.parseInt(parts[0]);
people.get(lastReceiver).money += remaining;
people.get(lastReceiver).count++;
}
// 转换为List并排序
List<Person> result = new ArrayList<>(people.values());
Collections.sort(result, (a, b) -> {
if(a.money != b.money) {
return Double.compare(b.money, a.money);
}
if(a.count != b.count) {
return b.count - a.count;
}
return a.id - b.id;
});
// 输出结果
for(Person p : result) {
System.out.printf("%d %.2f\n", p.id, p.money);
}
}
}
5. 性能优化与测试技巧
5.1 时间复杂度分析
该解法的时间复杂度主要来自:
- 输入处理:O(K*M),其中M是平均每个红包的抢包人数
- 排序:O(N log N)
总体复杂度在题目约束下是可接受的(N≤10^4,K≤10^3)
5.2 边界条件测试
在准备天梯赛时,需要特别注意以下测试用例:
- 极端值测试:N=1的最小情况
- 金额分配测试:红包金额不能被整除的情况
- 排序条件测试:多人收入相同、抢包次数也相同的情况
- 大额数据测试:接近题目给出的上限值
5.3 常见错误排查
- 编号越界:题目中人员编号是1-based,容易错误地使用0-based
- 精度丢失:直接使用double计算可能导致精度问题,如0.1+0.2≠0.3
- 排序条件顺序错误:三个排序条件的优先级不能颠倒
- 输入读取错误:在读取每个红包的抢包记录时,容易少读或多读行
6. 实际应用扩展
虽然题目设定是抢红包场景,但类似的统计排序模式可以应用于:
- 电商平台商家销售额排名
- 社交媒体用户互动量统计
- 游戏玩家积分排行榜
- 校园竞赛成绩统计
掌握这种数据处理模式后,可以轻松应对各种需要聚合统计和排序的业务场景。在实际开发中,可能还需要考虑:
- 数据持久化(数据库存储)
- 实时更新(消息队列处理)
- 分布式计算(大数据量场景)
提示:天梯赛时间有限,建议先写注释梳理思路再编码。我在实际参赛时,会先用5分钟在纸上画出数据流和关键步骤,这样编码时不容易遗漏条件。
