[编程之美] PSet2.12 快速寻找满足条件的两个数



题目:输入一个数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字。
       要求时间复杂度是O(n)。如果有多对数字的和等于输入的数字,输出任意一对即可。例如输入数组1、2、4、7、11、15和数字15。由于4+11=15,因此输出4和11。

方法一:暴力枚举法。

从数组中任意取出两个数字,计算两者之和是否为给定的数字。显然时间复杂度为N(N-1)/2,代码如下:

//暴力枚举,需要比较N(N-1)/2次
pair<int,int> findTwoNum(int Arr[] , int arrLen ,int sum)
{
	for(int i=0 ; i<arrLen ; i++)
		for(int j=i+1 ; j<arrLen ; j++){
			if(sum == Arr[i]+Arr[j]){
				return make_pair(Arr[i],Arr[j]);
			}
		}
	return make_pair(-1,-1);
}

方法二:变为查找数组中sum-Arr[i]是否存在。

      对每个Arr[i]都查找sum-Arr[i]是否存在,如果采用直接查询需要遍历一次数组。查询需要O(N),于是该算法总共需要O(N^2);如果先排序后查询,查询采用二分法。总的复杂度为O(NlogN)。

      当然还有更快的查询方法,hash表。因为给定一个数字,根据hash映射查找一个数字是否在数组中,只需要O(1)的时间。这样的话总体算法复杂度降低到O(N),但这种方法需要额外增加O(N)的hash表存储空间。某些情况下,用空间换时间不失为一个好方法。下面给出算法的实现代码:

//先快速排序O(NlogN)后二分查找sum-Arr[i]是否存在
//总复杂度O(NlogN)
void swap2Num(int &a , int &b)
{
	int temp = a;
	a=b;
	b=temp;
}
int Partition(int Arr[] , int low , int high){
	srand((unsigned)time(0));
	//注意:此处容易犯的错误是随机在数组取数与Arr[0]交换而不是Arr[low]
	swap2Num(Arr[low] , Arr[rand()%(high-low+1)+low]);//随机主元,分割更加均匀
	int pivot = Arr[low];
	while(low < high){
		while(low<high && Arr[high]>=pivot)
			high--;
		if(low<high)
			Arr[low] = Arr[high];
		while(low<high && Arr[low]<=pivot)
			low++;
		if(low<high)
			Arr[high] = Arr[low];
	}
	Arr[high] = pivot;
	return high;
}
void fastSort(int Arr[] , int low , int high)
{
	//递归截止条件
	if(high <= low)
		return;
	int mid = Partition(Arr , low , high);
	fastSort(Arr , low , mid-1);
	fastSort(Arr , mid+1 , high);
}
//返回数num在数组中下标,找不到返回-1
int bSearchNum(int Arr[] , int low , int high , int num)
{
	if(high < low){//递归到叶子节点后
		return -1;
	}
	int mid = low + (high-low)/2;//防止上溢
	if(Arr[mid] == num)
		return mid;
	if(Arr[mid] > num)
		bSearchNum(Arr , low , mid-1 , num);
	else
		bSearchNum(Arr , mid+1 , high ,num);
}
//
pair<int,int> findTwoNum(int Arr[] , int arrLen ,int sum)
{
	//---先快速排序O(NlogN)
	fastSort(Arr , 0 , arrLen-1);
	//---二分查找O(NlogN)
	int i;
	for(i=0 ; i<arrLen ; i++){
		if(-1 != bSearchNum(Arr , 0 , arrLen-1 , sum-Arr[i])){
			return make_pair(Arr[i] , sum-Arr[i]);
		}
	}
	return pair<int,int>(-1,-1);

}

方法三:先排序,后首尾相加与sum比较

      排序需要O(NlogN),然后令i=0,j=N-1,看arr[i]和arr[j]与sum的关系,如果大于sum,说明j太大了(数组排了序),于是j--;否则小于sum说明i太小了,于是i++。i某个时刻等于j时停止查找,于是只需要遍历一次O(N)就能得到最后结果。两步加起来时间复杂度O(NlogN)。下面是实现代码:

//使用首尾相加的方法判定
pair<int,int> findTwoNum(int Arr[] , int arrLen ,int sum)
{
	//---先快速排序O(NlogN)
	fastSort(Arr , 0 , arrLen-1);
	//---再指针移位O(N)
	int i=0 ;
	int j=arrLen-1;
	while(i<j){
		if(Arr[i]+Arr[j] == sum)
			return make_pair(Arr[i] , Arr[j]);
		if(Arr[i]+Arr[j] > sum)
			j--;
		else
			i++;
	}
	return make_pair(-1,-1);
}

int main()
{
	int Arr[9] = {2,5,6,8,1,9,50,33,12};
	pair <int,int>p = findTwoNum(Arr , 9 , 45);
	cout<<p.first<<" "<<p.second<<endl;
	return 0;
}

扩展问题:

1.如果把这个问题中“两个数字”改成“三个数字”或“任意个数字”时,你的解是多少呢?

          答:对于ai+aj+ak = sum,由于sum-ak有N个,而对于ai+aj=subSum这样的问题可以采用前面的方法三,双指针法可以在O(N)复杂度内得到相应的ai与aj,由于外循环从N个sum-ak中取一个,内循环采用双指针法。因此总复杂度O(NlogN)+O(N)*O(N) = O(N^2),代码如下:

//使用双指针法查找三个数之和是否存在,不存在返回-1,-1,-1
struct result{
	result(int a,int b,int c):a1(a),a2(b),a3(c){}
	result(){}
	int a1;
	int a2;
	int a3;
};
result findTwoNum(int Arr[] , int arrLen ,int sum)
{
	//---先快速排序O(NlogN)
	fastSort(Arr , 0 , arrLen-1);
	//---再对每个sum-ak指针移位找subSum-aj
	//注意要排除已用过的ak
	int subSum;
	int low,high;

	for(int i=0 ; i<arrLen ; i++){
		subSum = sum-Arr[i];
		low = 0;
		high = arrLen-1;
		while(low<high){
			if(low == i)
				low++;
			if(high == i)
				high--;

			if(Arr[low]+Arr[high] == subSum)
				return result(Arr[i],Arr[low],Arr[high]);
			else if(Arr[low]+Arr[high] > subSum)
				high--;
			else
				low++;
		}
	}
	return result(-1,-1,-1);
}

int main()
{
	int Arr[9] = {2,5,6,8,1,9,50,33,12};
	result res;
	res = findTwoNum(Arr , 9 , 13);
	cout<<res.a1<<" "<<res.a2<<" "<<res.a3<<endl;
	return 0;
}

2.如果完全相等的一堆数字找不到,能否找出和最接近解?

       答:如果找不到结果,可以保存作差的结果,取最小的哪一堆就行了。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值