首页 > 代码库 > 求数组中和为给定值的任意两个数

求数组中和为给定值的任意两个数

转载请注明出处:http://blog.csdn.net/ns_code/article/details/24933341


    题目:

    输入一个已经按升序排序过的数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字。要求时间复杂度是O(n)。如果有多对数字的和等于输入的数字,输出任意一对即可。

    例如输入数组1、2、4、7、11、15和数字15。由于4+11=15,因此输出4和11。

    思路

    最直接的做法是暴力法,两个for循环,时间复杂度为O(n*n),但是这样没有充分利用升序数组这一前提。我们假设数组为A,长度为len,给定的和为sum,最好的方法是先用数组的第一个数A[low]和最后一个数A[high]相加,看是否等于sum,如果等于sum,则找到了一组数,返回true,如果大于sum,则将较大的数向前移动一位,即high--,此时变成了第一个和倒数第二个数相加,如果小于sum,则将较小的数向后移动一位,即low++,此时变成了第二个和最后一个数相加,依此类推,如果low==high时还未找到和为sum的一组数,则返回false。该算法的时间复杂度为O(n),空间复杂度为O(1)。

    针对该方法需要给出一些证明,证明如下:

    对于一个升序数列A1,A2,...Ak k>=3,如果A1+Ak大于sum,那么考察k-1个数对和A1+Ak,A2+Ak,...Ak-1+Ak有sum<A1+Ak<=A2+Ak<=,...<=Ak-1+Ak,也就是说,Ak与数列中其它任何数的和都不可能等于sum,因此抛弃Ak这个数,对结果没有影响。A1+Ak如果小于sum的话,同理抛弃A1这个数对结果没有影响。

    该方法对任意的整数数组都适合。

    完整代码

/****************************************************************************************************
题目:输入一个已经按升序排序过的数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字。
要求时间复杂度是O(n)。如果有多对数字的和等于输入的数字,输出任意一对即可。
例如输入数组1、2、4、7、11、15和数字15。由于4+11=15,因此输出4和11。
*****************************************************************************************************/
#include<stdio.h>

/*
在升序数组A中找出和为sum的任意两个元素,保存在a和b中
*/
bool FindTwoNumSum(int *A,int len,int sum,int *a,int *b)
{
	if(A==NULL || len<2)
		return false;
	int low = 0;
	int high = len-1;
	while(low<high)
	{
		if(A[low]+A[high] == sum)
		{
			*a = A[low];
			*b = A[high];
			return true;
		}
		else if(A[low]+A[high] < sum)
			low++;
		else
			high--;
	}
	return false;
}

int main()
{
	int A[] = {1,3,7,9,12,15,17,18,19,20};
	int len = 10;
	int sum = 24;
	int a,b;
	if(FindTwoNumSum(A,len,sum,&a,&b))
		printf("Find two nums,they are:\n%d and %d\n",a,b);
	else
		printf("Not find\n");
	return 0;
}
    测试结果
    
    我们现在来拓展一下,假设数组是乱序的,而且规定数组中的元素全部为非负整数,同样给定一个数sum,在数组中找出任意两个数,使二者的和为sum。
    因为题目中限定了数组中的元素为非负整数,因此我们可以用哈希数组,开辟一个长度为sum的bool数组B[sum],并全部初始化为false,对数组A进行一次遍历,如果当前的元素A[i]大于sum,则直接跳过,否则,继续作如下判断,如果B[A[i]]为false,则将B[sum-A[i]]置为ture,这样当继续向后遍历时,如果有B[A[i]]为true,则有符合条件的两个数,分别为A[i]和sum-A[i]。如果遍历A结束后依然没有发现有B[A[i]]为true的元素,则说明找不到符合条件的元素。
    该算法的时间复杂度为O(n),但空间复杂度为O(sum)。或者如果知道非负整数数组A的最大值为MAX,则也可以开辟一个MAX大小的bool数组,思路是一样的。
    完整代码如下:
/****************************************************************************************************
题目:输入一个无序的非负整数数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字。
要求时间复杂度是O(n)。如果有多对数字的和等于输入的数字,输出任意一对即可。
例如输入数组1、7、4、11、6、15和数字15。由于4+11=15,因此输出4和11。
*****************************************************************************************************/
#include<stdio.h>
#include<stdlib.h>

/*
在无序数组A中找出和为sum的任意两个元素,保存在a和b中
*/
bool FindTwoNumSum(int *A,int len,int sum,int *a,int *b)
{
	if(A==NULL || len<2)
		return false;
	//各元素均被初始化为false的bool数组
	bool *B = (bool*)calloc(sum,sizeof(bool));
	if(B == NULL)
		exit(EXIT_FAILURE);
	int i;
	for(i=0;i<len;i++)
	{
		if(A[i]>sum)
			continue;
		if(B[A[i]] == false)
			B[sum-A[i]] = true;
		else
		{
			*a = A[i];
			*b = sum-A[i];
			free(B);
			B = NULL;
			return true;
		}
	}
	free(B);
	B = NULL;
	return false;
}

int main()
{
	int A[] = {19,3,9,7,12,20,17,18,1,16};
	int len = 10;
	int sum = 24;
	int a,b;
	if(FindTwoNumSum(A,len,sum,&a,&b))
		printf("Find two nums,they are:\n%d and %d\n",a,b);
	else
		printf("Not find\n");
	return 0;
}
    测试结果: