leetcode-master/problems/0452.用最少数量的箭引爆气球.md

304 lines
10 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="https://code-thinking-1253855093.file.myqcloud.com/pics/20210924105952.png" width="1000"/>
</a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 452. 用最少数量的箭引爆气球
[力扣题目链接](https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/)
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。
一支弓箭可以沿着 x 轴从不同点完全垂直地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstartxend 且满足  xstart ≤ x ≤ xend则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
给你一个数组 points ,其中 points [i] = [xstart,xend] ,返回引爆所有气球所必须射出的最小弓箭数。
示例 1
* 输入points = [[10,16],[2,8],[1,6],[7,12]]
* 输出2
* 解释对于该样例x = 6 可以射爆 [2,8],[1,6] 两个气球,以及 x = 11 射爆另外两个气球
示例 2
* 输入points = [[1,2],[3,4],[5,6],[7,8]]
* 输出4
示例 3
* 输入points = [[1,2],[2,3],[3,4],[4,5]]
* 输出2
示例 4
* 输入points = [[1,2]]
* 输出1
示例 5
* 输入points = [[2,3],[2,3]]
* 输出1
提示:
* 0 <= points.length <= 10^4
* points[i].length == 2
* -2^31 <= xstart < xend <= 2^31 - 1
## 思路
如何使用最少的弓箭呢
直觉上来看貌似只射重叠最多的气球用的弓箭一定最少那么有没有当前重叠了三个气球我射两个留下一个和后面的一起射这样弓箭用的更少的情况呢
尝试一下举反例发现没有这种情况
那么就试一试贪心吧局部最优当气球出现重叠一起射所用弓箭最少全局最优把所有气球射爆所用弓箭最少
**算法确定下来了,那么如何模拟气球射爆的过程呢?是在数组中移除元素还是做标记呢?**
如果真实的模拟射气球的过程应该射一个气球数组就remove一个元素这样最直观毕竟气球被射了
但仔细思考一下就发现如果把气球排序之后从前到后遍历气球被射过的气球仅仅跳过就行了没有必要让气球数组remote气球只要记录一下箭的数量就可以了
以上为思考过程已经确定下来使用贪心了那么开始解题
**为了让气球尽可能的重叠,需要对数组进行排序**
那么按照气球起始位置排序还是按照气球终止位置排序呢
其实都可以只不过对应的遍历顺序不同我就按照气球的起始位置排序了
既然按照起始位置排序那么就从前向后遍历气球数组靠左尽可能让气球重复
从前向后遍历遇到重叠的气球了怎么办
**如果气球重叠了,重叠气球中右边边界的最小值 之前的区间一定需要一个弓箭**
以题目示例 [[10,16],[2,8],[1,6],[7,12]]为例如图方便起见已经排序
![452.用最少数量的箭引爆气球](https://img-blog.csdnimg.cn/20201123101929791.png)
可以看出首先第一组重叠气球一定是需要一个箭气球3的左边界大于了 第一组重叠气球的最小右边界所以再需要一支箭来射气球3了
C++代码如下
```CPP
class Solution {
private:
static bool cmp(const vector<int>& a, const vector<int>& b) {
return a[0] < b[0];
}
public:
int findMinArrowShots(vector<vector<int>>& points) {
if (points.size() == 0) return 0;
sort(points.begin(), points.end(), cmp);
int result = 1; // points 不为空至少需要一支箭
for (int i = 1; i < points.size(); i++) {
if (points[i][0] > points[i - 1][1]) { // 气球i和气球i-1不挨着注意这里不是>=
result++; // 需要一支箭
}
else { // 气球i和气球i-1挨着
points[i][1] = min(points[i - 1][1], points[i][1]); // 更新重叠气球最小右边界
}
}
return result;
}
};
```
* 时间复杂度O(nlog n)因为有一个快排
* 空间复杂度O(1)有一个快排最差情况(倒序)需要n次递归调用因此确实需要O(n)的栈空间
可以看出代码并不复杂
## 注意事项
注意题目中说的是满足 xstart x xend则该气球会被引爆那么说明两个气球挨在一起不重叠也可以一起射爆
所以代码中 `if (points[i][0] > points[i - 1][1])` 不能是>=
## 总结
这道题目贪心的思路很简单也很直接,就是重复的一起射了,但本题我认为是有难度的。
就算思路都想好了,模拟射气球的过程,很多同学真的要去模拟了,实时把气球从数组中移走,这么写的话就复杂了。
而且寻找重复的气球,寻找重叠气球最小右边界,其实都有代码技巧。
贪心题目有时候就是这样,看起来很简单,思路很直接,但是一写代码就感觉贼复杂无从下手。
这里其实是需要代码功底的,那代码功底怎么练?
**多看多写多总结!**
## 其他语言版本
### Java
```java
/**
时间复杂度 : O(NlogN) 排序需要 O(NlogN) 的复杂度
空间复杂度 : O(logN) java所使用的内置函数用的是快速排序需要 logN 的空间
*/
class Solution {
public int findMinArrowShots(int[][] points) {
if (points.length == 0) return 0;
//用x[0] - y[0] 会大于2147483647 造成整型溢出
Arrays.sort(points, (x, y) -> Integer.compare(x[0], y[0]));
//count = 1 因为最少需要一个箭来射击第一个气球
int count = 1;
//重叠气球的最小右边界
int leftmostRightBound = points[0][1];
//如果下一个气球的左边界大于最小右边界
if (points[i][0] > leftmostRightBound ) {
//增加一次射击
count++;
leftmostRightBound = points[i][1];
//不然就更新最小右边界
} else {
leftmostRightBound = Math.min(leftmostRightBound , points[i][1]);
}
}
return count;
}
}
```
### Python
```python
class Solution:
def findMinArrowShots(self, points: List[List[int]]) -> int:
if len(points) == 0: return 0
points.sort(key=lambda x: x[0])
result = 1
for i in range(1, len(points)):
if points[i][0] > points[i - 1][1]: # 气球i和气球i-1不挨着注意这里不是>=
result += 1
else:
points[i][1] = min(points[i - 1][1], points[i][1]) # 更新重叠气球最小右边界
return result
```
### Go
```golang
func findMinArrowShots(points [][]int) int {
var res int =1//弓箭数
//先按照第一位排序
sort.Slice(points,func (i,j int) bool{
return points[i][0]<points[j][0]
})
for i:=1;i<len(points);i++{
if points[i-1][1]<points[i][0]{//如果前一位的右边界小于后一位的左边界,则一定不重合
res++
}else{
points[i][1] = min(points[i - 1][1], points[i][1]); // 更新重叠气球最小右边界,覆盖该位置的值,留到下一步使用
}
}
return res
}
func min(a,b int) int{
if a>b{
return b
}
return a
}
```
### Javascript
```Javascript
var findMinArrowShots = function(points) {
points.sort((a, b) => {
return a[0] - b[0]
})
let result = 1
for(let i = 1; i < points.length; i++) {
if(points[i][0] > points[i - 1][1]) {
result++
} else {
points[i][1] = Math.min(points[i - 1][1], points[i][1])
}
}
return result
};
```
### TypeScript
```typescript
function findMinArrowShots(points: number[][]): number {
const length: number = points.length;
if (length === 0) return 0;
points.sort((a, b) => a[0] - b[0]);
let resCount: number = 1;
let right: number = points[0][1]; // 右边界
let tempPoint: number[];
for (let i = 1; i < length; i++) {
tempPoint = points[i];
if (tempPoint[0] > right) {
resCount++;
right = tempPoint[1];
} else {
right = Math.min(right, tempPoint[1]);
}
}
return resCount;
};
```
### C
```c
int cmp(const void *a,const void *b)
{
return ((*((int**)a))[0] > (*((int**)b))[0]);
}
int findMinArrowShots(int** points, int pointsSize, int* pointsColSize){
//将points数组作升序排序
qsort(points, pointsSize, sizeof(points[0]),cmp);
int arrowNum = 1;
int i = 1;
for(i = 1; i < pointsSize; i++) {
//若前一个气球与当前气球不重叠,证明需要增加箭的数量
if(points[i][0] > points[i-1][1])
arrowNum++;
else
//若前一个气球与当前气球重叠判断并更新最小的x_end
points[i][1] = points[i][1] > points[i-1][1] ? points[i-1][1] : points[i][1];
}
return arrowNum;
}
```
### Rust
```Rust
use std::cmp;
impl Solution {
pub fn find_min_arrow_shots(mut points: Vec<Vec<i32>>) -> i32 {
if points.is_empty() {
return 0;
}
points.sort_by_key(|point| point[0]);
let size = points.len();
let mut count = 1;
for i in 1..size {
if points[i][0] > points[i-1][1] {
count += 1;
} else {
points[i][1] = cmp::min(points[i][1], points[i-1][1]);
}
}
return count;
}
}
```
-----------------------
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码一.jpg width=500> </img></div>