本文转自:http://zhedahht.blog.163.com/blog/static/25411174200732711051101/
http://zhedahht.blog.163.com/blog/static/2541117420072143251809/
http://blog.csdn.net/hackbuteer1/article/details/6699642
题目:输入一个已经按升序排序过的数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字。要求时间复杂度是O(n)。如果有多对数字的和等于输入的数字,输出任意一对即可。
例如输入数组1、2、4、7、11、15和数字15。由于4+11=15,因此输出4和11。
分析:如果我们不考虑时间复杂度,最直观的想法莫过去先在数组中固定一个数字,再依次判断数组中剩下的n-1个数字与它的和是不是等于输入的数字。可惜这种思路需要的时间复杂度是O(n2)。
我们假设现在随便在数组中找到两个数。如果它们的和等于输入的数字,那太好了,我们找到了要找的两个数字;如果小于输入的数字呢?我们希望两个数字的和再大一点。由于数组已经排好序了,我们是不是可以把较小的数字的往后面移动一个数字?因为排在后面的数字要大一些,那么两个数字的和也要大一些,就有可能等于输入的数字了;同样,当两个数字的和大于输入的数字的时候,我们把较大的数字往前移动,因为排在数组前面的数字要小一些,它们的和就有可能等于输入的数字了。
我们把前面的思路整理一下:最初我们找到数组的第一个数字和最后一个数字。当两个数字的和大于输入的数字时,把较大的数字往前移动;当两个数字的和小于数字时,把较小的数字往后移动;当相等时,打完收工。这样扫描的顺序是从数组的两端向数组的中间扫描。
代码如下:
#include <iostream>
using namespace std;
bool invaluedInput = false;
bool SumOfTwoNumber(int data[],int len,int sum,int& num1,int& num2)
{
if(NULL == data || len<=0)
{
invaluedInput = true;
return false;
}
invaluedInput = false;
int start = 0;
int end = len-1;
//扫描的顺序是从数组的两端向数组的中间扫描。
while(start < len)
{
if(data[start] + data[end] > sum)
end--;
else if(data[start] + data[end] < sum)
start++;
else
{
num1 = data[start];
num2 = data[end];
break;
}
}
if(start >= end)
return false;
else
return true;
}
int main()
{
int data[] = {1, 2, 4, 7, 11, 15};
int len = sizeof(data)/sizeof(int);
int sum = 15;
int num1 = 0;
int num2 = 0;
bool found = SumOfTwoNumber(data,len,sum,num1,num2);
if(true != found)
cout<<"Not found!"<<endl;
else
cout<<num1<<"+"<<num2<<"="<<sum<<endl;
}
扩展问题
1、如果把这个问题中的“两个数字”改成“三个数字”或“任意个数字”时,你的解是什么呢?
可以这样,对于每一个arr[i](0<=i<=n-1),求SumOfTwoNumber(arr,len,sum-arr[i],num1,num2).。如果SumOfTwoNumber成功找到有num1与num2的和为sum-arr[i],则此时和为sum的三个数找到:arr[i]、num1、num2。中心思想就是对于数组中每一个arr[i],求另外两个和为sum-arr[i]的数,直到找到为止。最坏情况下需要遍历一遍数组。
题目:输入一个正数n,输出所有和为n连续正数序列。
例如输入15,由于1+2+3+4+5=4+5+6=7+8=15,所以输出3个连续序列1-5、4-6和7-8。
分析:借用上面那道题的思想,我们用两个数small和big分别表示序列的最小值和最大值。首先把small初始化为1,big初始化为2。如果从small到big的序列的和大于n的话,我们向右移动small,相当于从序列中去掉较小的数字。如果从small到big的序列的和小于n的话,我们向右移动big,相当于向序列中添加big的下一个数字。一直到small等于(1+n)/2,因为序列至少要有两个数字。
代码如下:
#include <stdio.h>
#include <iostream>
using namespace std;
bool ContinuesSquenceWithSum(const int sum)
{
bool found = false;
if(sum < 3)
{
return false;
}
int i;
int first = 1;
int second = 2;
int tmpSum = first;
int middle = (1 + sum)/2;
while(first < middle)
{
tmpSum += second;
if(sum == tmpSum)
{
for(i=first; i<=second; i++)
printf("%d ",i);
printf("\n");
found = true;
}
else if(tmpSum > sum)
{
while((tmpSum > sum) && (first < middle))
{
tmpSum -= first;
first ++;
if(sum == tmpSum)
{
for(i=first; i<=second; i++)
printf("%d ",i);
printf("\n");
found = true;
}
}
}
second++;
}
return found;
}
int main()
{
int sum = 100;
bool found = ContinuesSquenceWithSum(sum);
if(!found)
cout<<"No found!"<<endl;
return 0;
}
在上面的代码中,求连续序列的和应用了一个小技巧。通常我们可以用循环求一个连续序列的和,但考虑到每一次操作之后的序列和操作之前的序列相比大部分数字都是一样的,只是增加或者减少了一个数字,因此我们可以在前一个序列的和的基础上求操作之后的序列的和。这样就可以减少很多不必要的运算,从而提高代码的效率。