一、题目描述
AI识别到面板上有N(1 ≤ N ≤ 100)个指示灯,灯大小一样,任意两个之间无重叠。
由于AI识别误差,每次别到的指示灯位置可能有差异,以4个坐标值描述AI识别的指示灯的大小和位置(左上角x1,y1,右下角x2,y2),
请输出先行后列排序的指示灯的编号,排序规则:
每次在尚未排序的灯中挑选最高的灯作为的基准灯;
找出和基准灯属于同一行所有的灯进行排序。两个灯高低偏差不超过灯半径算同一行(即两个灯坐标的差 ≤ 灯高度的一半)。
二、输入描述
第一行为N,表示灯的个数 接下来N行,每行为1个灯的坐标信息,格式为:
编号 x1 y1 x2 y2
- 编号全局唯一
- 1 ≤ 编号 ≤ 100
- 0 ≤ x1 < x2 ≤ 1000
- 0 ≤ y1 < y2 ≤ 1000
三、输出描述
排序后的编号列表,编号之间以空格分隔
示例1
输入:
5
1 0 0 2 2
2 6 1 8 3
3 3 2 5 4
5 5 4 7 6
4 0 4 2 6
输出:
1 2 3 4 5
四、代码实现
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Scanner;
/**
* @author code5bug
*/
class Light {
int no, x1, y1, x2, y2;
int height;
public Light(int no, int x1, int y1, int x2, int y2) {
this.no = no;
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.height = y2 - y1;
}
public int getHeight() {
return height;
}
public int getX1() {
return x1;
}
@Override
public String toString() {
return "Light{" +
"no=" + no +
", x1=" + x1 +
", y1=" + y1 +
", x2=" + x2 +
", y2=" + y2 +
", height=" + height +
'}';
}
}
public class LightSorter {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
if (N <= 0) {
System.out.println("Invalid input: N must be greater than 0.");
return;
}
List<Light> lights = new ArrayList<>();
for (int i = 0; i < N; i++) {
int no = scanner.nextInt();
int x1 = scanner.nextInt();
int y1 = scanner.nextInt();
int x2 = scanner.nextInt();
int y2 = scanner.nextInt();
if (no <= 0 || x1 < 0 || y1 < 0 || x2 < 0 || y2 < 0) {
System.out.println("Invalid input: All values must be non-negative and no must be greater than 0.");
return;
}
lights.add(new Light(no, x1, y1, x2, y2));
}
// 指定编号的指示灯是否已选择
boolean[] selected = new boolean[N + 1];
// 排序后的编号列表
List<Integer> sortedRst = new ArrayList<>();
while (true) {
// 在尚未排序的灯中挑选最高的灯作为的基准灯
Light baseLight = null;
for (Light light : lights) {
if (selected[light.no]) continue;
if (baseLight == null || baseLight.y1 > light.y1) {
baseLight = light;
}
}
if (baseLight == null) break;
// 找出和基准灯属于同一行所有的灯进行排序。两个灯高低偏差不超过灯半径算同一行
// (即两个灯坐标的差 灯高度的一半)。
List<Light> lineLights = new ArrayList<>();
// 灯半径
double lightRadius = baseLight.getHeight() / 2.0;
for (Light light : lights) {
if (!selected[light.no] && Math.abs(baseLight.y1 - light.y1) <= lightRadius) {
lineLights.add(light);
}
}
// 对同一行的灯进行排序,根据x1坐标
lineLights.sort(Comparator.comparingInt(Light::getX1));
// 将排序后的灯编号加入结果列表,并标记为已选择
for (Light light : lineLights) {
sortedRst.add(light.no);
selected[light.no] = true;
}
}
// 输出排序后的灯编号
for (int no : sortedRst) {
System.out.print(no + " ");
}
}
}
五、运行示例解析
输入
5
1 0 0 2 2
2 6 1 8 3
3 3 2 5 4
5 5 4 7 6
4 0 4 2 6
解析步骤
1、读取输入:
- 第一行输入 5 表示有 5 个灯。
- 接下来的 5 行分别表示每个灯的编号 no 和坐标 (x1, y1, x2, y2)。
2、初始化灯列表: - 创建一个 List 来存储所有灯的信息。
- 读取每个灯的信息并创建 Light 对象,添加到列表中。
3、输入验证: - 检查 N 是否大于 0。
- 检查每个灯的编号 no 是否大于 0,坐标 x1, y1, x2, y2 是否非负。
4、初始化辅助数据结构: - boolean[] selected 用于记录每个灯是否已被选择。
- List sortedRst 用于存储排序后的灯编号。
5、主循环: - 在每次循环中,找到尚未选择的最低的灯作为基准灯。
- 找出与基准灯在同一行的所有灯(高度偏差不超过灯半径)。
- 对同一行的灯按 x1 坐标进行排序。
- 将排序后的灯编号加入结果列表,并标记为已选择。
6、输出结果: - 输出排序后的灯编号。
具体执行过程
1、读取输入并初始化灯列表:
List<Light> lights = new ArrayList<>();
lights.add(new Light(1, 0, 0, 2, 2)); // Light{no=1, x1=0, y1=0, x2=2, y2=2, height=2}
lights.add(new Light(2, 6, 1, 8, 3)); // Light{no=2, x1=6, y1=1, x2=8, y2=3, height=2}
lights.add(new Light(3, 3, 2, 5, 4)); // Light{no=3, x1=3, y1=2, x2=5, y2=4, height=2}
lights.add(new Light(5, 5, 4, 7, 6)); // Light{no=5, x1=5, y1=4, x2=7, y2=6, height=2}
lights.add(new Light(4, 0, 4, 2, 6)); // Light{no=4, x1=0, y1=4, x2=2, y2=6, height=2}
2、第一次循环:
- 基准灯:Light{no=1, x1=0, y1=0, x2=2, y2=2, height=2}
- 同一行的灯:Light{no=1, x1=0, y1=0, x2=2, y2=2, height=2}
- 排序后:Light{no=1, x1=0, y1=0, x2=2, y2=2, height=2}
- 结果列表:[1]
- 标记 selected[1] = true
3、第二次循环:
- 基准灯:Light{no=2, x1=6, y1=1, x2=8, y2=3, height=2}
- 同一行的灯:Light{no=2, x1=6, y1=1, x2=8, y2=3, height=2}
- 排序后:Light{no=2, x1=6, y1=1, x2=8, y2=3, height=2}
- 结果列表:[1, 2]
- 标记 selected[2] = true
4、第三次循环:
- 基准灯:Light{no=3, x1=3, y1=2, x2=5, y2=4, height=2}
- 同一行的灯:Light{no=3, x1=3, y1=2, x2=5, y2=4, height=2}
- 排序后:Light{no=3, x1=3, y1=2, x2=5, y2=4, height=2}
- 结果列表:[1, 2, 3]
- 标记 selected[3] = true
5、第四次循环:
- 基准灯:Light{no=5, x1=5, y1=4, x2=7, y2=6, height=2}
- 同一行的灯:Light{no=5, x1=5, y1=4, x2=7, y2=6, height=2}, Light{no=4, x1=0, y1=4, x2=2, y2=6, height=2}
- 排序后:Light{no=4, x1=0, y1=4, x2=2, y2=6, height=2}, Light{no=5, x1=5, y1=4, x2=7, y2=6, height=2}
- 结果列表:[1, 2, 3, 4, 5]
- 标记 selected[4] = true, selected[5] = true
6、输出结果:
1 2 3 4 5
六、解析步骤
-
初始化:
- 读取整数
N
(灯的数量)。 - 创建一个
Light
对象的列表lights
。 - 创建一个布尔数组
selected
,用于跟踪哪些灯已经被排序(或选中),长度为N + 1
(因为灯编号从1开始)。 - 创建一个整数列表
sortedRst
,用于存储排序后的灯编号。
- 读取整数
-
读取灯的数据:
- 读取每个灯的编号(
no
)、左上角坐标(x1
,y1
)和右下角坐标(x2
,y2
),并创建Light
对象添加到lights
列表中。 - 检查输入的有效性(确保所有值都是非负的,且编号大于0)。在本例中,所有输入都是有效的。
- 读取每个灯的编号(
-
排序过程:
- 使用一个
while
循环来重复以下步骤,直到所有灯都被排序(即baseLight
为null
)。 - 在尚未排序的灯中,找到
y1
坐标最小的灯作为基准灯baseLight
- 计算基准灯的
height
(即y2 - y1
)和lightRadius
(即高度的一半)。 - 创建一个新列表
lineLights
,用于存储与基准灯在同一行的所有灯。 - 遍历所有灯,如果灯未被选中且其
y1
坐标与基准灯的y1
坐标之差不超过lightRadius
,则将该灯添加到lineLights
列表中。 - 对
lineLights
列表中的灯按照x1
坐标进行排序。 - 将排序后的灯编号添加到
sortedRst
列表中,并将这些灯在selected
数组中标记为已选中。
- 使用一个
-
输出排序后的灯编号:
- 遍历
sortedRst
列表,输出每个灯的编号。
- 遍历
七、注意
- 在我的代码中,实际上是在找
y1
最大的灯作为基准灯,而不是最小的。如果您的意图是找最小的,需要反转比较条件(即baseLight == null || baseLight.y1 < light.y1
)。 - 在本例中,由于所有灯的
y1
坐标都是唯一的,因此没有真正的“同一行”灯需要处理。但是,如果y1
坐标不是唯一的,并且两个灯的y1
坐标之差在lightRadius
之内,那么它们将被视为同一行并进行排序。 - 性能优化:可以使用优先队列(PriorityQueue)来管理未选择的灯,提高查找基准灯的效率。