leetcode:452. 用最少数量的箭引爆气球

题目来源

题目描述

在这里插入图片描述

在这里插入图片描述

class Solution {
public:
    int findMinArrowShots(vector<vector<int>>& points) {

    }
};

题目解析

问题:要求当前位置下(不许排序),要把所有的箭头引爆,需要使用多少箭

  • 遍历所有数组,从第一个气球开始,如果可以引爆其他气球,就引爆他们
  • 因为对于当前最小的区间来说,无论我按照什么顺序去射,早晚都需要引爆它
  • 但是我们要弓箭最小,应该怎么排列,怎么设计才能最少
    • 对于单独的一个气球,我们至少需要使用一次弓箭
    • 对于多个气球,我们在进行射击时,可以尽可能多的再射击到其他气球
    • 一支箭射穿多个区间,它们都是有重合的。我们应该尽量把有重合的区间堆在一起,给上一箭。

为了方便在一次遍历中识别重合,先进行排序,是按照区间左端还是右端升序呢

其实都可以,但是建议按照右端点进行排序

  • 如果按左端升序排序:
    • 有交集关系的区间中,有的区间结束位置比较早
    • 第一箭的位置需要进行迭代判断,取区间 [0, 6]、[1, 2] 中结束位置最小的位置,即 arrow_pos = min(points[i][1], arrow_pos),然后再判断接下来的区间是否能够引爆。
[0..................6]
   [1..2]    
             [4..5]
         Λ
  [-------|]
     [----|------] //这俩哥们一个箭够了
        [-|---------] //如果第三个是这样的,那一根箭还是够的
          |  [-------]  //如果是这样的呢,不行了,因为这个的start小于第一行那个end了,所以得再加一根
  • 如果对右端点进行排序:
    • 箭的位置一开始就确定了,不需要再改变和判断箭的位置,直接判断区间即可。
       Λ  
  [-----|]     
[-------|---]  
  [-----|---]
     [--|---]
        |  [-----]  // 只有此时需要再来一根

我们来分析下按照**左端点(起始位置)**进行排序的过程

问题:从前向后遍历遇到重叠的气球了怎么办

举个例子,假设我们已经对气球排序了,而且得到了[1, 6],[2, 8],[7, 12],[9, 10]。
在这里插入图片描述
我们对这四个气球进行遍历

  • 当我们遍历到第一个气球时,可以获得一个射击区间 [ 1 , 6 ] [1, 6] [1,6],这这个区间里,一定可以射中第一个气球
    在这里插入图片描述
  • 当我们遍历到第二个气球时,由于第二个区间的左端点小于射击区间的右边界,而右端点大于射击区间的左边界,因此我们重新确立射击区间[2, 6]

在这里插入图片描述

  • 当我们遍历到第三个气球时,其左端点大于射击区间的右边界,因此我们在原先的射击区间中是无法打中第三个气球的,我们需要重新开辟一块射击区间 [7, 12]。

在这里插入图片描述

  • 当我们遍历到第四个气球时,其左端点小于射击区间的右边界,右端点也小于射击区间的右边界,因此需要缩小射击区间。

在这里插入图片描述

  • 至此,我们至少需要两次射击才能引爆气球。

在以上的分析中,我们事实上是不需要考虑射击区间的左边界的,因为我们已经对气球进行了排序,所以每次的区间确定,左区间总是要改变的。我们核心要考虑的是右区间和新遍历的气球的左右两个端点的关系:

  • 如果新气球的左端点大于射击区间的右边界,那么我们就需要开辟一个新的区间(新的箭)
  • 如果新气球的左端点小于射击区间的右边界,这里又要分为两种情况:
    • 如果右端点大于射击区间的右边界,那么我们的射击区间的右边界无需变化;
    • 如果右端点小于射击区间的右边界,那么我们射击区间的右边界就要向左移动,以新气球的右端点为准,确立新的边界。

代码实现:

  • 先根据start进行升序排序,然后从索引1开始遍历数组,比较当前下标i位置的左坐标points[i][0]和前一个右坐标points[i-1][1]的大小关系
    • 如果points[i]-1[1] < points[i+1][0],说明不重叠,此时需要一支新的箭
    • 如果points[i-1][1] >= points[i][0],说明有重叠区间,此时我们需要更新当前气球的右坐标用来标记重合区间的右坐标,方便接下来对比时判断是否重合,即points[i][1] = min(points[i][1] , points[i-1][1])
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;
    }
};

按照end进行排序

步骤:

  • 前面的气球必须要消耗掉
  • 射未爆第一个气球的尾部
  • 消耗掉此次射击连带的气球,算法实现上就是跳过这个气球
  • 箭头再次指向未爆第一个气球的尾部
class Solution {
public:
    int findMinArrowShots(vector<vector<int>>& points) {
        if (points.empty()) {
            return 0;
        }
        sort(points.begin(), points.end(), [](const vector<int>& u, const vector<int>& v) {
            return u[1] < v[1];
        });
        int pos = points[0][1];
        int ans = 1;
        for (const vector<int>& balloon: points) {
            if (balloon[0] > pos) {  // 开始区间 > 末尾区间,说明不挨着了
                pos = balloon[1];  //因为是按照末尾排序的
                ++ans;
            }
        }
        return ans;
    }
};

思路

弓箭的起始位置和结束位置可以看做是一段区间,直观上来看,为了使用最少的弓箭数,可以尽量射中区间重叠最多的地方。

所以问题变为了:如何寻找区间重叠最多的地方,也就是区间交集最多的地方。

区间调度问题:给定一些有交集的区间,形如[[start1, end1],[start2,end2],…],求最大不相交的区间数量。

对于此题,如果最多有n个不相交的区间,则需要的箭至少为n个。

对区间调度问题,先按各区间的end升序排序,再从前向后遍历区间,对比后面区间的start和前面区间的end,检查是否有交集。

【动态规划+图解思路】
参考
参考

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
给定一个整数数组 nums 和一个目标值 target,要求在数组中找出两个数的和等于目标值,并返回这两个数的索引。 思路1:暴力法 最简单的思路是使用两层循环遍历数组的所有组合,判断两个数的和是否等于目标值。如果等于目标值,则返回这两个数的索引。 此方法的时间复杂度为O(n^2),空间复杂度为O(1)。 思路2:哈希表 为了优化时间复杂度,可以使用哈希表来存储数组中的元素和对应的索引。遍历数组,对于每个元素nums[i],我们可以通过计算target - nums[i]的值,查找哈希表中是否存在这个差值。 如果存在,则说明找到了两个数的和等于目标值,返回它们的索引。如果不存在,将当前元素nums[i]和它的索引存入哈希表中。 此方法的时间复杂度为O(n),空间复杂度为O(n)。 思路3:双指针 如果数组已经排序,可以使用双指针的方法来求解。假设数组从小到大排序,定义左指针left指向数组的第一个元素,右指针right指向数组的最后一个元素。 如果当前两个指针指向的数的和等于目标值,则返回它们的索引。如果和小于目标值,则将左指针右移一位,使得和增大;如果和大于目标值,则将右指针左移一位,使得和减小。 继续移动指针,直到找到两个数的和等于目标值或者左指针超过了右指针。 此方法的时间复杂度为O(nlogn),空间复杂度为O(1)。 以上三种方法都可以解决问题,选择合适的方法取决于具体的应用场景和要求。如果数组规模较小并且不需要考虑额外的空间使用,则暴力法是最简单的方法。如果数组较大或者需要优化时间复杂度,则哈希表或双指针方法更合适。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值