转载请注明出处:http://blog.csdn.net/ns_code/article/details/24933341
-
题目描述:
- 输入一个递增排序的数组和一个数字S,在数组中查找两个数,是的他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
-
输入:
-
每个测试案例包括两行:第一行包含一个整数n和k,n表示数组中的元素个数,k表示两数之和。其中1 <= n <= 10^6,k为int第二行包含n个整数,每个数组均为int类型。
-
输出:
- 对应每个测试案例,输出两个数,小的先输出。如果找不到,则输出“-1 -1”
-
样例输入:
-
6 15 1 2 4 7 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这个数对结果没有影响。
该方法对任意的整数数组都适合,另外,要输出乘积最小的一组,没必要将所有的结果保存起来,我们由下面我们高中时非常熟悉的数学公式可以证明最左边和最右边的两个符合要求的数的乘积最小。
当a+b = c时,ab<=(a+b)的平方/4,当且仅当a==b时,ab取得最大值,二者相差越远,乘积越小。
aC代码如下:
/****************************************************************************************************
题目:输入一个已经按升序排序过的数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字。
要求时间复杂度是O(n)。如果有多对数字的和等于输入的数字,输出任意一对即可。
例如输入数组1、2、4、7、11、15和数字15。由于4+11=15,因此输出4和11。
*****************************************************************************************************/
#include<stdio.h>
#include<stdbool.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 n,k;
static int A[1000000];
while(scanf("%d %d",&n,&k) != EOF)
{
int i;
for(i=0;i<n;i++)
scanf("%d",A+i);
int a,b;
bool can = FindTwoNumSum(A,n,k,&a,&b);
if(can)
printf("%d %d\n",a,b);
else
printf("-1 -1\n");
}
return 0;
}
/**************************************************************
Problem: 1352
User: mmc_maodun
Language: C
Result: Accepted
Time:1460 ms
Memory:4820 kb
****************************************************************/
我们现在来拓展一下,假设数组是乱序的,而且规定数组中的元素全部为非负整数,同样给定一个数sum,在数组中找出任意两个数,使二者的和为sum。
/****************************************************************************************************
题目:输入一个无序的非负整数数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字。
要求时间复杂度是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;
}
测试结果: