1.题目
问题描述
在唐门,外门弟子唐三发现了一种强大的暗器技巧,名为暴雨梨花针,它能在一条直线上摧毁所有目标。唐三在一本古籍中学习到了这门技巧,为了练习,他设置了若干个靶子在二维平面上。每个靶子是一条垂直于X轴的线段,由三个参数 x_{left}
,x_{right}
,y
描述,其中 y
是固定的高度,x_{left}
和 x_{right}
表示线段在X轴上的起止位置。
唐三的目标是使用尽可能少的暴雨梨花针来击中所有靶子。由于暴雨梨花针的珍贵,每次射击必须经济有效。每次射击暴雨梨花针都将从某个 x
值发射,能垂直上升到 y = 100
。
请你帮助唐三计算最少需要多少次射击,才能保证击中所有靶子。结果需对给定的 P
取余。
测试样例
样例1:
输入:k = 4, p = 100, target = [[10, 26, 3], [4, 8, 29], [1, 5, 8], [9, 9, 9]]
输出:
3
样例2:
输入:k = 3, p = 100, target = [[10, 26, 3], [4, 8, 29], [1, 5, 8]]
输出:
2
样例3:
输入:k = 5, p = 100, target = [[5, 15, 5], [1, 2, 3], [20, 25, 2], [6, 18, 2], [30, 40, 1]]
输出:
4
样例4:
输入:k = 2, p = 100, target = [[0, 10, 5], [15, 20, 3]]
输出:
2
样例5:
输入:k = 6, p = 100, target = [[3, 8, 10], [5, 10, 12], [1, 3, 14], [15, 18, 10], [20, 25, 10], [8, 12, 10]]
输出:
4
2.样例分析
暴雨梨花针能在一条直线上摧毁所有目标
样例 1 最少暴雨梨花针数量: 3
样例 2 最少暴雨梨花针数量: 2
3.思路
这道题和435. 无重叠区间 - 力扣(LeetCode)非常相似,重叠的区间用一根暴雨梨花针。
- 排序靶子: 首先,我们将所有的靶子按其左边界进行排序,如果左边界相同,则按右边界排序。这一步是为了确保在射击时能够尽量减少箭矢的数量。
- 贪心策略: 我们初始化箭矢的范围为第一个靶子的范围(即左边界和右边界)。然后遍历其余靶子,如果当前靶子的左边界小于等于当前射击范围的右边界(即可以用当前箭矢射中该靶子),则更新当前箭矢的范围。如果当前靶子的左边界大于当前射击范围的右边界,说明需要使用新的箭矢,增加箭矢的数量并更新射击范围。
- 计算箭矢数量: 最后,返回箭矢数量对
p
的余数。
4.代码
绘制靶子图形
import matplotlib.pyplot as plt
# 设置 matplotlib 支持中文显示
plt.rcParams['font.sans-serif'] = ['SimHei'] # 使用黑体字体
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
def minimum_arrows(targets):
if not targets:
return 0
# 排序:首先按右边界排序,再按y坐标排序
targets.sort(key=lambda x: (x[1], x[2])) # 排序根据右边界和y坐标
arrows = 1
end = targets[0][1]
for target in targets[1:]:
if target[0] > end:
arrows += 1
end = target[1]
return arrows
def plot_targets(targets, title):
plt.figure()
for target in targets:
x_left, x_right, y = target
# 如果是点(x_left == x_right),绘制一个小的圆点
if x_left == x_right:
plt.plot(x_left, y, 'ro') # 红色圆点表示点
else:
plt.plot([x_left, x_right], [y, y], 'b-') # 绘制靶子线段
plt.xlabel('X 轴')
plt.ylabel('Y 轴')
plt.title(title)
plt.grid(True)
plt.show()
# 样例数据
samples = [
{
"k": 4,
"p": 100,
"targets": [[10, 26, 3], [4, 8, 29], [1, 5, 8], [9, 9, 9]],
"title": "样例 1"
},
{
"k": 3,
"p": 100,
"targets": [[10, 26, 3], [4, 8, 29], [1, 5, 8]],
"title": "样例 2"
},
{
"k": 5,
"p": 100,
"targets": [[5, 15, 5], [1, 2, 3], [20, 25, 2], [6, 18, 2], [30, 40, 1]],
"title": "样例 3"
},
{
"k": 2,
"p": 100,
"targets": [[0, 10, 5], [15, 20, 3]],
"title": "样例 4"
},
{
"k": 6,
"p": 100,
"targets": [[3, 8, 10], [5, 10, 12], [1, 3, 14], [15, 18, 10], [20, 25, 10], [8, 12, 10]],
"title": "样例 5"
}
]
for sample in samples:
k = sample["k"]
p = sample["p"]
targets = sample["targets"]
title = sample["title"]
# 计算最少的针数量
result = minimum_arrows(targets) % p
print(f"{title} 最少暴雨梨花针数量: {result}")
# 绘制靶子图
plot_targets(targets, title)
算法
import java.util.Arrays;
public class Main {
public static int solution(int k, int p, int[][] target) {
// Please write your code here、
// 贪心算法:首先按靶子的左边界(x_left)排序,如果左边界相同,则按右边界(x_right)排序
Arrays.sort(target, (o1, o2) -> {
if (o1[0] > o2[0]) {
return 1; // 如果o1的左边界大于o2的左边界,o1排在后面
} else if (o1[0] < o2[0]) {
return -1;
} else {
return o1[1] - o2[1]; // 如果左边界相同,则按右边界排序
}
});
// 初始化:第一个靶子的位置,使用第一支箭矢,范围从左到右
int l = target[0][0], r = target[0][1], result = 1; // `result`记录使用的箭矢数量
// 从第二个靶子开始,遍历所有靶子,判断是否能用当前的箭矢覆盖
for (int i = 1; i < k; i++) {
// 如果当前靶子的左边界在当前射击范围内,说明可以用当前箭矢继续覆盖
if (target[i][0] <= r) {
// 更新当前箭矢的覆盖范围:新的左边界是两个靶子左边界的较大值,右边界是两个靶子右边界的较小值
l = Math.max(l, target[i][0]);
r = Math.min(r, target[i][1]);
} else {
// 如果当前靶子不能被当前箭矢覆盖,增加一支新的箭矢,并更新射击范围
result++;
// 新的箭矢需要从当前靶子的左边界射出,并以当前靶子的右边界为范围
l = target[i][0];
r = target[i][1];
}
}
return result % p;
}
public static void main(String[] args) {
// You can add more test cases here
int[][] testTarget1 = {
{10, 26, 3},
{4, 8, 29},
{1, 5, 8},
{9, 9, 9}
};
int[][] testTarget2 = {
{10, 26, 3},
{4, 8, 29},
{1, 5, 8}
};
int[][] testTarget3 = {
{13, 20, 2},
{15, 39, 3},
{34, 89, 6},
{2, 10, 1},
{0, 87, 2},
{23, 49, 3},
{2, 45, 9},
{9, 98, 0},
{3, 12, 9},
{35, 45, 21},
{51, 67, 23},
{37, 42, 54},
{55, 76, 7},
{2, 13, 6},
{29, 31, 9},
{10, 32, 1}
};
System.out.println(solution(4, 100, testTarget1) == 3);
System.out.println(solution(3, 100, testTarget2) == 2);
System.out.println(solution(16, 100, testTarget3) == 5);
}
}
return o1[1] - o2[1];
- 返回值为负:当
o1[1]
小于o2[1]
时,o1
会排在o2
前面。 - 返回值为正:当
o1[1]
大于o2[1]
时,o1
会排在o2
后面。 - 返回值为零:当
o1[1]
等于o2[1]
时,它们的顺序保持不变。
5.参考资料
MarsCode刷题--65.唐门绝技:暴雨梨花针最少发射次数_唐门绝技:暴雨梨花针最少发射次数 问题描述 在唐门,外门弟子唐三发现了一种强大的-优快云博客