编程之美:第二章 数字之魅 2.19区间重合判断

/*
区间重合判断:
给定一个源区间[x,y](y>=x)和N个无序的目标区间[x1,y1],[x2,y2],[x3,y3],...,[xn,yn],判断源区间[x,y]是不是在目标区间内(也即[x,y]是否属于任意[xi,yi])

例如:给定源区间[1,6]和一组无序的目标区间[2,3][1,2][3,9],即可认为区间[1,6]在区间[2,3][1,2][3,9]内(因为目标区间实际上是[1,9])



解法1:
本质上将源区间上不断减去给定的目标区间,如果减完,说明完全覆盖
本质在于目标区间的处理。直接的思路即将源区间[x,y](y>=x)和N个无序的目标区间[x1,y1][x2,y2]...[xn,yn]逐个投影到坐标轴上,只考察源区间未被覆盖的部分
如果所有的目标区间全部投影完毕,仍然有源区间没有被覆盖,那么源区间就不在目标区间之内。
源区间[1,6],那么最初未被覆盖的部分为{[1,6]},将按照顺序考察目标区间[2,3][1,2][3,9]。
将目标区间[2,3]投影到坐标轴,那么未被覆盖的部分为{[1,6]将变成{[1,2],[3,6]}
将目标区间[1,2]投影到坐标轴,那么未被覆盖的部分为{[3,6]},将目标区间[3,9]投影到坐标轴,那么未被覆盖的部分为空。
每次操作,尚未被覆盖的区间数组大小最多增加1,每投影一个新区间,计算哪些源区间数组被覆盖需要O(log2N)的时间复杂度,但是更新尚未被覆盖的区间数组
需要O(N)的时间复杂度,所以总的时间复杂度为O(N^2)


解法2:
对现有的数组进行预处理(如排序和合并),将无序的目标区间合并成几个有序的目标区间,进行区间之间的比较。

先合并,即先将目标区间数组按照X轴坐标从小到大排序,如[2,3][1,2][3,9] ->[1,2][2,3][3,9];接着扫描排序后的目标区间数组,将这些区间合并成若干个不相交
的区间,如[1,2][2,3][3,9]->[1,9]

然后用二分查找,判定源区间[x,y]是否被合并后的这些互不相交的区间中的某一个包含。如[1,6]被[1,9]包含,则可以说明源区间在目标区间之内。

排序:O(nLogN)
合并:O(N)
查找:log2N
所以总的时间复杂度为)(N*log2N + k*log2N),k为查询的次数,合并目标区间数组的初始化操作只需要进行一次

输入:
3(多少个比较区间)
1 6 (源区间)
2 3 (目标区间)
1 2
3 9

3
1 6
2 3
1 2
4 9

4
1 6
1 3
1 2
2 4
5 9
输出:
Yes
No
*/

/*
关键:
1 对现有的数组进行预处理(如排序和合并),将无序的目标区间合并成几个有序的目标区间,进行区间之间的比较。

先合并,即先将目标区间数组按照X轴坐标从小到大排序,如[2,3][1,2][3,9] ->[1,2][2,3][3,9];接着扫描排序后的目标区间数组,将这些区间合并成若干个不相交
的区间,如[1,2][2,3][3,9]->[1,9]

然后用二分查找,判定源区间[x,y]是否被合并后的这些互不相交的区间中的某一个包含。
2 	for(int i = 1 ; i < iLen ; i++)//归并区间的大体算法参见删除指定字符的算法,向前归并,我们可以采用如果可以归并,就一直归并,而不是两两归并的方法
	{
		if(iEnd >= secArr[i]._iBeg)//可以归并
		{
			if(iEnd <= secArr[i]._iEnd)//非完全重合,终点选取后一个区间的终点
			{
				iEnd = secArr[i]._iEnd;
			}
			else//完全重合,终点仍然是前一个区间的终点,不变
			{

			}
			if( i == iLen -1)//注意,如果这个时候i已经抵达末尾,需要立即归并
			{
				secArr[iCnt++].set(iBeg,iEnd);
				iBeg = secArr[i]._iBeg;//重新设置iBeg,iEnd为当前区间的起点和终点
				iEnd = secArr[i]._iEnd;
			}
		}
		else//不可以归并时,这之后产生新的区间,并且向后继续归并。
		{
			secArr[iCnt++].set(iBeg,iEnd);
			iBeg = secArr[i]._iBeg;//重新设置iBeg,iEnd为当前区间的起点和终点
3 int lower_bound(Section* secArr,int low,int high,const Section& secOri)
	//查找原区间在目标区间中的位置,关键是要找到一个区间,目标区间的起点<=源区间的起点,目标区间的终点>=源区间的终点,我们可以采用二分搜索,通过起点
	//值来来作为搜索条件,实际上是lower_bound的一个变体,如果能够找到时,比较终点,若目标区间终点<源区间终点,不是寻找前面一个区间(如果存在的话)
	//能否包含,如果能包含就可以,如果这两个区间都不能包含,那就是错误的了。这里lower_bound返回的下标要么是起点都相同的区间的下标,要么是排序后
	//比源区间大一号的区间下标,所以这里要比较两次
*/

#include <stdio.h>
#include <algorithm>

using namespace std;

const int MAXSIZE = 10000;

typedef struct Section
{
	int _iBeg;
	int _iEnd;
	bool operator < (const Section& sec) const 
	{
		if(_iBeg != sec._iBeg)
		{
			return _iBeg < sec._iBeg;
		}
		else
		{
			return _iEnd < sec._iEnd;
		}
	}
	bool operator == (const Section& sec) const 
	{
		if(_iBeg == sec._iBeg)//只要起始区间相同,就认为相同
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	void set(int iBeg,int iEnd)
	{
		_iBeg = iBeg;
		_iEnd = iEnd;
	}
}Section;


bool canMerge(const Section& secFront,const Section& secBack)//判断前面的区间是否与后面的区间能够归并
{
	if(secFront._iEnd < secBack._iBeg || secFront._iBeg > secBack._iEnd)//A区间的后面比B区间的前面都要小,或A区间的前面比B区间的后面都要大,不能合并
	{
		return false;
	}
	else
	{
		return true;
	}
}

int max(int a,int b)
{
	return a > b ? a : b;
}

int mergeSection(Section* secArr,int iLen)
{
	int iCnt = 0;
	int iEnd = secArr[0]._iEnd;
	int iBeg = secArr[0]._iBeg;
	for(int i = 1 ; i < iLen ; i++)//归并区间的大体算法参见删除指定字符的算法,向前归并,我们可以采用如果可以归并,就一直归并,而不是两两归并的方法
	{
		if(iEnd >= secArr[i]._iBeg)//可以归并
		{
			if(iEnd <= secArr[i]._iEnd)//非完全重合,终点选取后一个区间的终点
			{
				iEnd = secArr[i]._iEnd;
			}
			else//完全重合,终点仍然是前一个区间的终点,不变
			{

			}
			//if( i == iLen -1)//注意,如果这个时候i已经抵达末尾,需要立即归并
			//{
			//	secArr[iCnt++].set(iBeg,iEnd);
			//	iBeg = secArr[i]._iBeg;//重新设置iBeg,iEnd为当前区间的起点和终点
			//	iEnd = secArr[i]._iEnd;
			//}
		}
		else//不可以归并时,这之后产生新的区间,并且向后继续归并。
		{
			secArr[iCnt++].set(iBeg,iEnd);
			iBeg = secArr[i]._iBeg;//重新设置iBeg,iEnd为当前区间的起点和终点
			iEnd = secArr[i]._iEnd;
		}
	}
	secArr[iCnt++].set(iBeg,iEnd);//漏掉了对最后一次区间的归并
	return iCnt;//返回归并后区间总数
}

/*
lower_bound:当iVal > iArr[mid]时,mid是不可能的,low = mid+1,
            当iVal < iArr[mid]时,mid是可能,high = mid
			当iVal = iArr[mid]时,mid是肯能的,但是需要向前寻找,high = mid
			综上:iVal > iArr[mid],low = mid + 1
			                  else,high = mid 
*/
int lower_bound(Section* secArr,int low,int high,const Section& secOri)
	//查找原区间在目标区间中的位置,关键是要找到一个区间,目标区间的起点<=源区间的起点,目标区间的终点>=源区间的终点,我们可以采用二分搜索,通过起点
	//值来来作为搜索条件,实际上是lower_bound的一个变体,如果能够找到时,比较终点,若目标区间终点<源区间终点,不是寻找前面一个区间(如果存在的话)
	//能否包含,如果能包含就可以,如果这两个区间都不能包含,那就是错误的了。这里lower_bound返回的下标要么是起点都相同的区间的下标,要么是排序后
	//比源区间大一号的区间下标,所以这里要比较两次
	
{
	int mid;
	while(low < high)
	{
		mid = low + (high - low)/2;
		if(secArr[mid]._iBeg < secOri._iBeg)//直接用起始点去比较
		{
			low = mid + 1;
		}
		else
		{
			high = mid;
		}
	}
	return low;
}

bool isContain(const Section& secFront,const Section& secBack)//判断后面一个区间是否完全包含令一个区间
{
	if(secBack._iBeg <= secFront._iBeg && secBack._iEnd >= secFront._iEnd)
	{
		return true;
	}
	else
	{
		return false;
	}
}

void process()
{
	int n;
	while(EOF != scanf("%d",&n))
	{
		Section secOri;
		scanf("%d %d",&secOri._iBeg,&secOri._iEnd);
		Section secGoal[MAXSIZE];
		for(int i = 0 ; i < n ; i++)
		{
			scanf("%d %d",&secGoal[i]._iBeg,&secGoal[i]._iEnd);
		}
		sort(secGoal,secGoal + n);//排序
		int iSize = mergeSection(secGoal,n);//归并能归并的区间,必须满足能被包含的条件,而且归并的必定是前后两个相邻的区间
		int iIndex = lower_bound(secGoal,0,iSize,secOri);//二分查找
		if(isContain(secOri,secGoal[iIndex]) || (iIndex-1 >= 0 && isContain(secOri,secGoal[iIndex-1]) ) )
		{
			printf("Yes\n");
		}
		else
		{
			printf("No\n");
		}
	}
}

int main(int argc,char* argv[])
{
	process();
	getchar();
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值