一、问题描述
有N(3 ≤ N < 10000)个运动员,他们的id为0到N-1,他们的实力由一组整数表示。他们之间进行比赛,需要决出冠亚军。比赛的规则是0号和1号比赛,2号和3号比赛,以此类推,每一轮,相邻的运动员进行比赛,获胜的进入下一轮;实力值大的获胜,实力值相等的情况,id小的情况下获胜;轮空的直接进入下一轮。
比赛规则总结如下:
- 初始轮次中,0 号与 1 号比赛、2 号与 3 号比赛,以此类推,相邻运动员两两对决
- 每一轮比赛,实力值大的选手获胜;若实力值相等,id 更小的选手获胜
- 轮空的选手(当当前轮次人数为奇数时,最后一位选手)直接进入下一轮
二、输入输出描述
输入描述
- 输入一行N个数字代表N的运动员的实力值(0<=实力值<=10000000000)。
输出描述
- 输出冠亚季军的id,用空格隔开
三、示例
| 输入 | 2 3 4 5 |
| 输出 | 3 1 2 |
| 说明 |
第一轮比赛, id为0实力值为2的运动员和id为1实力值为3的运动员比赛,1号胜出进入下一轮争夺冠亚军, id为2的运动员和id为3的运动员比赛,3号胜出进入下一轮争夺冠亚军, 冠亚军比赛,3号胜1号, 故冠军为3号,亚军为1号,2号与0号,比赛进行季军的争夺,2号实力值为4,0号实力值2,故2号胜出,得季军。冠亚季军为3 1 2。 |
四、解题思路
1. 核心思想
通过 “多轮两两晋级赛” 筛选强者,同时保留最近 3 轮的失败者队列(仅这部分人有资格竞争季军),最终通过队列分层确定冠、亚、季军 —— 避免全量排序,用 “淘汰制 + 队列缓存” 高效锁定获奖候选人。
2. 问题本质分析
题目本质是 “从无序集合中筛选 Top3”,但附加了特殊对决规则(序号小者在实力相等 / 劣势时获胜),且需避免冗余计算:
- 冠军:需通过多轮晋级赛决出(只有持续获胜到最后 1 人的才是冠军)。
- 亚军:只能是 “最后一轮输给冠军的人”(即冠军组的上一轮失败组)。
- 季军:只能是 “倒数第二轮的失败者”(即冠军组的上两轮失败组)—— 更早被淘汰的人实力不足以竞争季军,无需保留。
3. 核心逻辑
- 晋级机制:两两对决,按规则产生获胜组(晋级下一轮)和失败组(进入候选队列),奇数个时末尾直接晋级。
- 队列缓存:用链表维护最近 3 轮的组(头部是最新获胜组,尾部是更早的失败组),超过 3 组则剔除尾部(无缘季军)。
- 冠军锁定:循环晋级头部获胜组,直到其仅含 1 人(冠军)。
- 亚、季军锁定:队列第 2 组的唯一者为亚军,第 3 组排序后取最优者为季军。
4. 步骤拆解
-
输入解析与对象封装:
- 读取输入的实力数组,为每个元素绑定编号(索引),封装为
Sport对象列表。
- 读取输入的实力数组,为每个元素绑定编号(索引),封装为
-
初始晋级赛:
- 调用
promote方法,对所有运动员执行第一轮两两对决,产生初始的获胜组和失败组,存入结果队列(队列仅保留 3 组)。
- 调用
-
循环晋级决出冠军:
- 若队列头部的获胜组人数 > 1,说明冠军未决出,移除该获胜组再次执行
promote,将新的获胜组 / 失败组压入队列头部。 - 重复上述步骤,直到头部获胜组仅含 1 人(即冠军)。
- 若队列头部的获胜组人数 > 1,说明冠军未决出,移除该获胜组再次执行
-
确定亚、季军:
- 亚军:队列第 2 组(上一轮输给冠军的失败组)的唯一候选人。
- 季军:队列第 3 组(上两轮的失败组)按 “实力降序 + 编号升序” 排序,取第 1 名。
-
结果输出:
- 拼接冠军、亚军、季军的编号,返回最终结果。
五、代码实现
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Scanner;
public class Main {
// 运动员类
static class Sport {
int id; // 运动员的id
long strength; // 运动员的实力
public Sport(int id, long strength) {
this.id = id;
this.strength = strength;
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
long[] strengths = Arrays.stream(sc.nextLine().split(" ")).mapToLong(Long::parseLong).toArray();
System.out.println(getResult(strengths));
}
public static String getResult(long[] strength) {
// ans只记录三个组,冠军组,亚军组,季军组
LinkedList<ArrayList<Sport>> ans = new LinkedList<>();
// 将输入的实力值,转化为运动员集合
ArrayList<Sport> sports = new ArrayList<>();
for (int i = 0; i < strength.length; i++) sports.add(new Sport(i, strength[i]));
// 晋级赛
promote(sports, ans);
// 冠军组如果不是一个人,那么还需要取出冠军组继续进行晋级赛
while (ans.getFirst().size() > 1) {
promote(ans.removeFirst(), ans);
}
// 冠军
int first = ans.get(0).get(0).id;
// 亚军
int second = ans.get(1).get(0).id;
// 季军
ans.get(2)
.sort(
(a, b) ->
a.strength != b.strength ? b.strength - a.strength > 0 ? 1 : -1 : a.id - b.id);
int third = ans.get(2).get(0).id;
return first + " " + second + " " + third;
}
public static void promote(ArrayList<Sport> sports, LinkedList<ArrayList<Sport>> ans) {
// 记录获胜组
ArrayList<Sport> win = new ArrayList<>();
// 记录失败组
ArrayList<Sport> fail = new ArrayList<>();
for (int i = 1; i < sports.size(); i += 2) {
// 序号大的运动员
Sport major = sports.get(i);
// 序号小的运动员
Sport minor = sports.get(i - 1);
if (major.strength > minor.strength) {
win.add(major);
fail.add(minor);
} else {
// 如果序号大的运动员的实力 <= 序号小的运动员,则序号小的运动员获胜
win.add(minor);
fail.add(major);
}
}
// 如果晋级赛中运动员个数是奇数个,那么最后一个运动员直接晋级
if (sports.size() % 2 != 0) {
win.add(sports.get(sports.size() - 1));
}
// 依次头部压入失败组,获胜组,保证头部是获胜组
ans.addFirst(fail);
ans.addFirst(win);
// 如果保留组个数超过3个,那么需要将超过部分的组去掉,因为这部分人已经无缘季军
while (ans.size() > 3) ans.removeLast();
}
}
182

被折叠的 条评论
为什么被折叠?



