1. 天梯赛L2-009题目背景解析
PAT(Programming Ability Test)天梯赛是国内最具影响力的程序设计竞赛之一,由浙江大学计算机科学与技术学院主办。L2级别题目通常考察中级算法能力和工程实现水平,要求参赛者不仅能够解决问题,还要考虑时间空间复杂度等工程因素。
L2-009"抢红包"是一道模拟现实场景的经典题目,主要考察以下能力:
- 对现实业务场景的抽象建模能力
- 数据结构的选择与使用技巧
- 边界条件的处理意识
- Java集合框架的熟练运用
这道题源自微信红包的真实场景,但做了算法化的简化处理。题目会给出N个人之间发红包、抢红包的记录,要求最终按照特定规则排序输出每个人的收入情况。
提示:虽然题目描述的是"抢红包",但实际解题时不需要模拟实时抢红包过程,而是对既定记录进行统计计算,这是很多选手初期容易误解的地方。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 题目需求分析与建模
2.1 输入输出规范
典型输入格式示例:
code复制3
2 22 10
3 12 5
1 99 20
2 16 8
1 75 15
表示:
- 第一行3表示有3个人参与
- 后续每行格式为:发红包人编号 红包金额 红包个数
- 需要统计每个人最终的收入(抢到的-发出的)
输出要求:
- 按收入从高到低排序
- 收入相同按抢到红包个数从多到少排序
- 仍相同按人员编号升序排列
2.2 核心数据结构选择
这道题需要处理两类主要数据:
- 人员信息:需要记录每个人的收入、发出总额、抢到红包次数
- 红包记录:需要解析每条红包分发记录
推荐使用面向对象的方式建模:
java复制class Person implements Comparable<Person> {
int id;
int money; // 收入=收到-发出(可能为负)
int count; // 抢到红包次数
// 实现比较逻辑
@Override
public int compareTo(Person o) {
if(this.money != o.money) {
return o.money - this.money; // 收入降序
}
if(this.count != o.count) {
return o.count - this.count; // 次数降序
}
return this.id - o.id; // 编号升序
}
}
2.3 算法流程设计
处理流程可分为三个阶段:
- 初始化阶段:创建N个Person对象
- 处理红包记录:
- 解析每条记录:发红包人、总金额、红包个数
- 计算每个红包金额(整除处理)
- 更新发红包人的发出总额
- 更新抢红包人的收入及计数
- 排序输出阶段:
- 使用Collections.sort()排序
- 按格式输出结果
3. Java实现详解
3.1 完整代码实现
java复制import java.util.*;
public class Main {
static class Person implements Comparable<Person> {
int id;
int money = 0;
int count = 0;
Person(int id) {
this.id = id;
}
@Override
public int compareTo(Person o) {
if(this.money != o.money) {
return o.money - this.money;
}
if(this.count != o.count) {
return o.count - this.count;
}
return this.id - o.id;
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
Person[] people = new Person[n+1]; // 编号1~n
for(int i=1; i<=n; i++) {
people[i] = new Person(i);
}
for(int k=0; k<n; k++) {
int sender = sc.nextInt();
int total = sc.nextInt();
int num = sc.nextInt();
if(num == 0) continue;
int each = total / num;
people[sender].money -= total;
for(int i=0; i<num; i++) {
int receiver = sc.nextInt();
people[receiver].money += each;
people[receiver].count++;
}
// 处理除不尽的零头
people[sender].money += total % num;
}
List<Person> list = new ArrayList<>();
for(int i=1; i<=n; i++) {
list.add(people[i]);
}
Collections.sort(list);
for(Person p : list) {
System.out.printf("%d %.2f\n", p.id, p.money/100.0);
}
}
}
3.2 关键代码解析
-
金额处理技巧:
- 题目中金额以分为单位存储,避免浮点数精度问题
- 最后输出时除以100.0转换为元单位
- 红包金额计算使用整除,余数返还给发红包人
-
输入处理要点:
- 使用Scanner读取输入
- 人员数组从1开始索引(题目编号习惯)
- 每次读取完整的一条红包记录再处理
-
排序实现:
- 让Person类实现Comparable接口
- 重写compareTo方法定义排序规则
- 使用Collections.sort()自动排序
4. 常见问题与优化建议
4.1 典型错误排查
-
除零错误:
- 当红包个数为0时需要特殊处理
- 示例代码中通过if(num == 0) continue跳过
-
金额计算错误:
- 确保发红包人扣减的是总金额
- 每个抢红包人获得的是平分金额
- 余数要返还给发红包人
-
排序规则错误:
- 注意三重要求的优先级
- 比较器实现要完全匹配题目要求
4.2 性能优化建议
-
输入输出优化:
- 对于大规模数据,使用BufferedReader代替Scanner
- 使用StringBuilder构建输出再统一打印
-
数据结构优化:
- 如果人员数量很大(N>10^5),考虑使用更高效的结构
- 但本题常规N≤1000,普通数组即可
-
空间优化:
- 可以边读入边处理,不保存所有记录
- 但为代码清晰,示例选择了先存储后处理的方式
4.3 测试用例设计
建议验证以下边界情况:
- 只有1个人的情况
- 有人只发红包不抢红包
- 有人抢到0个红包
- 红包金额不能被整除的情况
- 多人收入相同的情况
示例测试用例:
code复制3
1 100 2 2 3
2 50 0
3 60 3 1 2 1
预期输出:
code复制1 -10.00
3 -20.00
2 30.00
5. 竞赛技巧与扩展思考
5.1 天梯赛答题策略
-
快速理解题意:
- 先看输入输出样例
- 画出示意图理清数据流向
-
模块化编程:
- 将问题分解为几个清晰的步骤
- 每个步骤用独立的方法实现
-
调试技巧:
- 使用print调试关键变量
- 准备边界测试用例
5.2 实际业务场景思考
真实红包系统还需考虑:
- 并发抢红包的锁机制
- 红包金额随机分配算法
- 数据库事务处理
- 分布式系统下的数据一致性
虽然竞赛题做了简化,但核心思路是一致的:准确建模、正确处理数据、考虑边界情况。这些能力在实际工程中同样重要。
我在实际竞赛中遇到这类问题时,通常会先在纸上画出数据流程图,明确每个环节的数据变化,然后再开始编码。这种方法可以避免很多因理解偏差导致的错误。另外,建议在编码前先考虑好排序规则的实现方式,因为这是本题的关键得分点。
