无重叠区间及用最少的箭射爆气球

无重叠区间及用最少的箭射爆气球

一:开胃菜

在开始所无重叠区间前先做一道简单的提来铺垫一下

给你很多形如 [start, end] 的闭区间, 请你设计⼀个算法,
算出这些区间中最多有⼏个互不相交的区间

int intervalSchedule(int[][] intvs) {}

举个例⼦, intvs = [[1,3], [2,4], [3,6]] , 这些区间最多有 2 个区间互不相交, 即 [[1,3], [3,6]] , 你的算法应该返回 2。 注意边界相同并不算相交。

这个问题有许多看起来不错的贪⼼思路, 却都不能得到正确答案。 ⽐如说:

也许我们可以每次选择可选区间中开始最早的那个? 但是可能存在某些区间开始很早, 但是很⻓, 使得我们错误地错过了⼀些短的区间

或者我们每次选择可选区间中最短的那个? 或者选择出现冲突最少的那个区间? 这些⽅案都能很容易举出反例, 不是正确的⽅案

正确的思路其实很简单, 可以分为以下三步:

  1. 从区间集合 intvs 中选择⼀个区间 x, 这个 x 是在当前所有区间中结束最早的(end 最⼩) 。

  2. 把所有与 x 区间相交的区间从区间集合 intvs 中删除。

  3. 重复步骤 1 和 2, 直到 intvs 为空为⽌。 之前选出的那些 x 就是最⼤不相交⼦集

把这个思路实现成算法的话, 可以按每个区间的 end 数值升序排序, 因为这样处理之后实现步骤 1 和步骤 2 都⽅便很多:

现在来实现算法, 对于步骤 1, 由于我们预先按照 end 排了序,所以选择x 是很容易的。 关键在于, 如何去除与 x 相交的区间, 选择下⼀轮循环的 x呢?

由于我们事先排了序不难发现所有与 x 相交的区间必然会与 x 的 end 相交; 如果⼀个区间不想与 x 的 end 相交, 它的 start 必须要⼤于(或等于) x 的 end

在这里插入图片描述

直接看代码:

public int intervalSchedule(int[][] intvs) {
	if (intvs.length == 0) 
		return 0;
	// 按 end 升序排序
	Arrays.sort(intvs, new Comparator<int[]>() 
	{
		public int compare(int[] a, int[] b) 
		{
			return a[1] - b[1];
		}
	}

	// 记录不相交区间的个数,初始化时⾄少有⼀个区间不相交
	int count = 1;
	
	// 排序后, 第⼀个区间就是 x
	int x_end = intvs[0][1];
	
	for (int[] interval : intvs) 
	{
		int start = interval[0];

		//代表这个区间和选择的区间不相交,更新相关变量
		if (start >= x_end)
		{
			// 找到下⼀个选择的区间了
			count++;
			x_end = interval[1];
		}
	} 
	return count;
}

二、无重叠区间

给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。

注意:

可以认为区间的终点总是大于它的起点。 区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。

示例 1:

输入: [ [1,2], [2,3], [3,4], [1,3] ]

输出: 1

解释: 移除 [1,3] 后,剩下的区间没有重叠。

示例 2:

输入: [ [1,2], [1,2], [1,2] ]

输出: 2

解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。

示例 3:

输入: [ [1,2], [2,3] ]

输出: 0

解释: 你不需要移除任何区间,因为它们已经是无重叠的了。

我们已经会求最多有⼏个区间不会重叠了, 那么剩下的不就是⾄少需要去除的区间吗?

int eraseOverlapIntervals(int[][] intervals) {
	int n = intervals.length;
	return n - intervalSchedule(intervals);
}

完整C++代码

class Solution {
public:
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        if (intervals.empty()) 
            return 0;

        //先对vector进行排序
        sort(intervals.begin(), intervals.end());

        //记录左边界,[start,end]相当于end位置的元素
        int left = intervals[0][1];

        //记录要移除的个数
        int res = 0;

        //循环测试
        for (int i = 1; i < intervals.size(); ++i) 
        {
            //如果intervals[i][0] < left(相当于上一个区间的 end 大于循环测试的start)代表两个区间重复,更新左边界及计数
            if (intervals[i][0] < left) 
            {
                ++res;

                //left在区间重复的情况下,左边界相当于取上一个区间的end 和 循环测试的某个区间的end的最小值
                left = min(left, intervals[i][1]);
            } 
            else 
            {
                //两个区间没有相交,直接判断下一个区间
                left = intervals[i][1];
            }
        }
        return res;
    }
};


三、用最少的箭射爆气球

在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。

一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。

Example:

输入:
[[10,16], [2,8], [1,6], [7,12]]

输出:
2

解释:
对于该样例,我们可以在x = 6(射爆[2,8],[1,6]两个气球)和 x = 11
(射爆另外两个气球)。

其实稍微思考⼀下, 这个问题和区间调度算法⼀模⼀样,只不过是气球变成了区间而已! 如果最多有 n 个不重叠的区间, 那么就⾄少需要 n 个箭头穿透所有区间:

在这里插入图片描述

只是有⼀点不⼀样, 在 intervalSchedule 算法中, 如果两个区间的边界触碰, 不算重叠; ⽽按照这道题⽬的描述(开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆), 箭头如果碰到⽓球的边界⽓球也会爆炸, 所以说相当于区间的边界触碰也算重叠:

在这里插入图片描述

所以只要将之前的算法稍作修改, 就是这道题⽬的答案:

int findMinArrowShots(int[][] intvs) {
//
	for (int[] interval : intvs) {
		int start = interval[0];
		// 把 >= 改成 > 就⾏了
		if (start > x_end) {
			count++;
			x_end = interval[1];
		}
	} 
	return count;
}

完整C++代码

class Solution {
public:
    int findMinArrowShots(vector<vector<int>>& points) {
        if(points.empty())
        {
            return 0;
        }

        //排序
        sort(points.begin(),points.end());

        //定义第一个区间的结束位置为end,后序区间都会和end进行比较
        int end = points[0][1];

        //用来计数
        int count = 1;

        //循环遍历所有区间
        for(int i = 1;i < points.size();i++)
        {
            //定义每个区间的开始位置为start
            int start = points[i][0];

            //如果start > end 说明两个区间没有重叠,更新相关变量
            if(start > end)
            {
                ++count;
                end = points[i][1];
            }
            else
            {
                //取当前end和重叠区间的end的较小值
                end = min(end,points[i][1]);
            }
        }
        return count;
    }
};
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页