力扣435.无重叠区间----贪心策略三法(一暴力贪心,一优化验证过程,一另辟蹊径反面思考)

435. 无重叠区间

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

注意:

可以认为区间的终点总是大于它的起点。
区间 [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

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

题解:

对于此题,只要以一种贪心的视角取看待即可。
首先由于给的数组是乱序的,所以不妨先将其排下序再进行计算。

那么是按每个数组的start值大小排序还是按照end值大小排序呢?
首先我们要明白,秉着贪心的原则,我们为了尽可能的去少删,故我们删去的应该是其end值较大的。

因为在以一个区间为基准,找到n个区间重合时时,我们必定要删去n-1个区间,只留下一个区间,而为了使留下的这个区间尽可能的不要再和别的区间“沾上关系”,故选择end值小的留下。
且在经过排序后,我们从第一个元素开始已经是end最小的了,所以只要发现与此元素的区间有重合的就直接删去即可,因为若留下end大的可能不只与该元素区间重合,可能还与别的区间重合。

可由一图加深理解:
在这里插入图片描述

图片来源:labuladong的算法小抄

因此下面是我第一次写出的代码,而我开始没有恰当考虑怎样去验证两个区间是重合的,因此代码较为繁杂,效率较低,且我着重于直接找到要删去的区间。

-------------注:下面的right相当于我原先提到的end,left相当于我原先提到的start

代码:

int cmp(void*_x,void*_y)
{
    int *x=*(int**)_x;
    int *y=*(int**)_y;
    return x[1]>y[1];
}
int eraseOverlapIntervals(int** intervals, int intervalsSize, int* intervalsColSize){
    if(intervalsSize==0)
        return 0;
    int sum = 0;//储存要删去的区间数目
    qsort(intervals,intervalsSize,sizeof(int*),cmp);//不排序会误删
    for(int i=0;i<intervalsSize;i++)//遍历一遍数组,即将每个数组都当成“基准”试一试
    {   
        if(intervals[i][0]==INT_MAX)//由下面可知此代表“删去”,所以“删去”的不用进行循环了
            continue;
        for(int j=0;j<intervalsSize;j++)//在进行一次循环找是否有重合的区间
        {
            if(i==j||intervals[j][0]==INT_MAX)//已经“删去”的和下标一样的不用进行循环了
                continue;
            if((intervals[j][0]>=intervals[i][0]&&intervals[j][0]<intervals[i][1])||(intervals[j][1]>intervals[i][0]&&intervals[j][1]<=intervals[i][1]))//此为重合的条件,也是此代码繁杂之处
            {
                if(intervals[j][1]>intervals[i][1])//比较end
                {
                    sum++;
                    intervals[j][1]=INT_MAX;//此代表“删去”的意思,因为c语言不能严格的执行删除操作
                    intervals[j][0]=INT_MAX;//所以我们把这个区间变成无穷远即可代表他是被删去了
                    continue;
                }
                else
                {
                    sum++;
                    intervals[i][1]=INT_MAX;//“删去”基准
                    intervals[i][0]=INT_MAX;
                    break;//若基准被“删去”了,则直接跳出循环进行下次即可,因为基准已经没了
                }
            }
        }
    }
    return sum;
}

在这里插入图片描述

后来由以前的“打气球问题”想到可以对重合区间的验证过程进行优化,
即要验证两个区间是否重合,因为我们已经按照end值排好序了,若作为基准区间的下一个区间其start值大于等于基准区间的end则一定不重合,若其start小于基准区间的end一定重合,这恰恰是因为我们已经按照end大小排好序了,所以基准区间的下一个区间的end必定是大于基准区间的end的。
所以把此验证过程转化到只比较下一个的start和基准的end即可。

于是写出了以下代码:

-------(注意的是,因为我们已经按照end排好序了,所以基准区间就从第一个开始即可,因为他必然是end最小的)。

int cmp(void*_x,void*_y)
{
    int *x=*(int**)_x;
    int *y=*(int**)_y;
    return x[1]>y[1];
}
int eraseOverlapIntervals(int** intervals, int intervalsSize, int* intervalsColSize){
    if(intervalsSize==0)
        return 0;
    int sum = 0;//储存要删去的区间数目
    qsort(intervals,intervalsSize,sizeof(int*),cmp);//不排序会误删
    for(int i=0;i+1<intervalsSize;i++)//遍历一遍数组,即将每个数组都当成“基准”试一试
    {
        if(intervals[i][1]==INT_MAX)//此为已经“删去”了,所以不用参与循环了
            continue;
        int right = intervals[i][1];//先储存基准区间的end
        int j = i+1;//不能改变i,所以设了j来完成接下来的循环找重合区间任务
        while(intervals[j][0]<right)
        {
            sum++;
            intervals[j][0]=INT_MAX;//"删除"操作
            intervals[j][1]=INT_MAX;
            j++;
            if(j==intervalsSize)//防止越界,所以跳出来
                break;
        }
    }
    return sum;
}

在这里插入图片描述

但两种方法都是着重于直接找应删去的区间数,其实可以另辟蹊径从反面来思考

即总区间数量我们已经知道了,我们只要求出不重合的区间数,那么二者相减即为所求。
不明白代码内改变基准位置的作用的话可以再看一下前面的图。

优化后如下:

int cmp(void*_x,void*_y)
{
    int *x=*(int**)_x;
    int *y=*(int**)_y;
    return x[1]>y[1];
}
int eraseOverlapIntervals(int** intervals, int intervalsSize, int* intervalsColSize){
    if(intervalsSize==0||intervalsSize==1)
        return 0;
    int sum = 1;//先储存一下不重合的区间,因为在区间总数大于1的情况下,不重合区间最少要有一个,因为我们是n个删掉n-1个
    qsort(intervals,intervalsSize,sizeof(int*),cmp);//不排序会误删
    int right=intervals[0][1];//将基准的end先储存起来
    for(int i=1;i<intervalsSize;i++)
    {
        if(intervals[i][0]>=right)//在以一个位置为基准的情况下找到了一个不与基准重合的区间,就要改变一下基准位置了
        {
            sum++;//证明多了个不重合的区间
            right=intervals[i][1];//改变“基准”的end值
        }
    }
    return intervalsSize-sum;
}

在这里插入图片描述