[Leetcode力扣 452] Minimum Number of Arrows to Burst Balloons

一、题目描述

给一堆二维闭区间表示气球,问最少用几支箭能把所有气球射爆

二、分析:戳气球问题等价于“不相交区间问题”

先介绍“不相交区间问题”:给出n个区间,从中挑选出尽可能多的区间,使这些区间两两没有交集。然后下个结论:戳气球问题等价于不相交区间问题,下面介绍为啥:

“不相交区间问题”找到的是n个区间中互不相交的区间,假设找到了k个不相交区间,那么必须有k支箭来射爆这些区间代表的气球;并且对于这每一个不相交区间,可能都会有与其重叠的其他区间存在,那么这k支箭中的每一支都可以找到一个合适的位置,射爆与每一个不相交区间相交的区间代表的气球,所以最少需要k支箭来射爆所有的气球。

还可以用证明一下上面的结论:假设需要k+1支箭才能射爆所有气球(易知至少需要k支箭,所以不需要假设k-1支箭的情况),那么多出来了一个区间(也就是气球),假设多出来的这个区间是一个不相交区间,那么和有k个不相交区间这一条件相悖,不可能;假设多出来的这个区间与某个区间相交,那么它一定和k个区间中的某一个相交,那么也不需要额外的一支箭。综上,想要射爆所有气球,那么最少使用k支箭即可。

在理解上还有一个小问题:“不相交区间问题”找到的是最多的不相交区间,而戳气球问题却要求我们找到戳爆所有气球的最少的箭数,这一个问题是“找最多”,另一个是“找最少”,这两个问题为啥会等价呢?其实,两个问题各自要找的“多少”的含义是截然不同的:区间问题中要求“最多的”不相交区间,是为了这个结果能够包括整个区间集合,试想,如果不要求“最多”,那么不相交区间永远选0个或者1个不就行了;而戳气球问题选“最少”是因为,如果选最多,那么总选n个不就行了。综上,这两个问题各自定义的“多少”含义完全不同,所以没必要在这上面纠结。

三、解决“不相交区间问题”的思路

(这里照搬一下《算法笔记》中提供的思路)

关键在于两点:(1)如果出现“区间包含”的情况,如图a所示,那么一定要选择被包含的区间,因为这样就有更大的空间容纳别的区间了,正所谓贪心的思想正体现在这里。(2)对于图b的情况,要能找到“铁定不和其他区间相交的部分”,比如说图中x1的虚线的右半部分,然后x1剩下的部分就变成了(1)的情况,那么选哪个就很简单了。

要达到(2)的效果,对某个区间端点进行排序即可,无论排序的是左端点还是右端点,是从大到小排还是从小到大排,都可以。下面以对区间左端点从小到大排序为例:

四、代码实现:对区间左端点从小到大排序

class Solution {
public:
    int findMinArrowShots(vector<vector<int>>& points) 
    {
        if(points.empty()) return 0;
        
        sort(points.begin(), points.end(), [](const vector<int>& a, const vector<int>& b){
                                                    return a[0] < b[0];
                                            });

        //right代表当前选择的区间的右边界,res是不相交区间个数
        int n = points.size(), right = points[0][1], res = 1;
        for(int i = 1; i < n; ++i)
        {
            //如果当前区间的左边界大于当前选择的区间的右边界,
            //说明我们又找到了一个不相交区间,并且right值要更新为该区间的右边界 
            if(points[i][0] > right)   
            {
                ++res;
                right = points[i][1];
            }
            //如果当前区间的左边界小于等于当前选择的区间的右边界,
            //说明当前区间铁定不是一个不相交区间,但是right值可能需要更新,
            //因为可能出现当前区间的右边界值小于right,此时应该选择更小的right值 
            else                       
                right = right < points[i][1] ? right : points[i][1];
        }
        return res;
    }
};

五、还有其余多种排序方法

1. 对区间左端点从大到小排序:

class Solution {
public:
    int findMinArrowShots(vector<vector<int>>& points) 
    {
        if(points.empty()) return 0;
        
        sort(points.begin(), points.end(), [](const vector<int>& a, const vector<int>& b){
                                                    return a[0] > b[0];
                                            });
        int n = points.size(), right = points[n-1][1], res = 1;
        for(int i = n - 2; i >= 0; --i)
        {
            if(points[i][0] > right)
            {
                ++res;
                right = points[i][1];
            }
            else
                right = right < points[i][1] ? right : points[i][1];
        }
        return res;
    }
};

2. 对区间右端点从小到大排序

class Solution {
public:
    int findMinArrowShots(vector<vector<int>>& points) 
    {
        if(points.empty()) return 0;
        
        sort(points.begin(), points.end(), [](const vector<int>& a, const vector<int>& b){
                                                    return a[1] < b[1];
                                            });
        int n = points.size(), left = points[n-1][0], res = 1;
        for(int i = n - 2; i >= 0; --i)
        {
            if(points[i][1] < left)
            {
                ++res;
                left = points[i][0];
            }
            else
                left = left > points[i][0] ? left : points[i][0];
        }
        return res;
    }
};

3. 对区间右端点从大到小排序

class Solution {
public:
    int findMinArrowShots(vector<vector<int>>& points) 
    {
        if(points.empty()) return 0;
        
        sort(points.begin(), points.end(), [](const vector<int>& a, const vector<int>& b){
                                                    return a[1] > b[1];
                                            });
        int n = points.size(), left = points[n-1][0], res = 1;
        for(int i = 0; i < n; ++i)
        {
            if(points[i][1] < left)
            {
                ++res;
                left = points[i][0];
            }
            else
                left = left > points[i][0] ? left : points[i][0];
        }
        return res;
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值