寻找和为定值的两个数

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

注意:这个题目需要特别注意给定的数组中元素是有序的还是无序的。

思路一:直接穷举,从数组中任意选取两个数,判定它们的和是否为输入的那个数字。此举复杂度为O(N^2)。耗时太长!

思路二:题目相当于,对每个a[i],查找sum-a[i]是否也在原始序列中,每一次要查找的时间都要花费为O(N),这样下来,最终找到两个数还是需要O(N^2)的复杂度。那如何提高查找判断的速度呢?答案是二分查找,可以将O(N)的查找时间提高到O(logN),这样对于N个a[i],都要花logN的时间去查找相对应的sum-a[i]是否在原始序列中,总的时间复杂度已降为O(N*logN),且空间复杂度为O(1)。(如果有序,直接二分O(N*logN),如果无序,先排序后二分,复杂度同样为O(N*logN+N*logN)=O(N*logN),空间总为O(1))。

思路三:如果数组是无序的,先排序(n*logn),然后用两个指针i,j,各自指向数组的首尾两端,令i=0,j=n-1,然后i++,j--,逐次判断a[i]+a[j]?=sum,如果某一刻a[i]+a[j]>sum,则要想办法让sum的值减小,所以此刻i不动,j--,如果某一刻a[i]+a[j]<sum,则要想办法让sum的值增大,所以此刻i++,j不动。所以,数组无序的时候,时间复杂度最终为O(n*logn+n)=O(n*logn),若原数组是有序的,则不需要事先的排序,直接O(n)搞定,且空间复杂度还是O(1),此思路是相对于上述所有思路的一种改进。(如果有序,直接两个指针两端扫描,时间O(N),如果无序,先排序后两端扫描,时间O(N*logN+N)=O(N*logN),空间始终都为O(1))。(与上述思路2相比,排序后的时间开销由之前的二分的n*logn降到了扫描的O(N))。

思路四:构造hash表。

给定一个数字,根据hash映射查找另一个数字是否也在数组中,只需用O(1)的时间,这样的话,查询n次,总的时间复杂度为O(N),但有个缺陷,就是构造hash额外增加了O(N)的空间。不过,空间换时间,仍不失为在时间要求较严格的情况下的一种好办法。

综上所述,若是数组有序的情况下,优先考虑两个指针两端扫描法,以达到最佳的时(O(N)),空(O(1))效应。否则,如果要排序的话,时间复杂度最快当然是只能达到N*logN,空间O(1)则是不在话下。

//寻找和为定值的两个数
//夹逼排序(以下的代码都是基于数组元素有序的)
//代码一:只输出一个满足条件的整数对
#include <iostream>

using namespace std;

void TwoSum(int *a, int length, int sum)
{
	//sort(a,a+length);   如果数组非有序的,那就事先排好序O(N*logN)
	int *begin, *end;
	begin = a;
	end = a + length - 1;
	while (begin<end)
	{
		if (*begin + *end == sum)
		{
			cout << *begin <<" "<< *end << endl;
			break;
		}
		else if (*begin + *end>sum)
		{
			end--;
		}
		else
		{
			begin++;
		}
	}
}

int main( )
{
	int sum = 11;//
	int max_index = 0;//最长回文子串的中心点位置
	int str[8] = {1,2,4,5,6,7,8,9};

	TwoSum(str, 8, sum);

	return 0;
}

//代码二:输出满足条件的所有整数对
#include <iostream>

using namespace std;

void TwoSum(int *a, int length, int sum)
{
	//sort(a,a+length);   如果数组非有序的,那就事先排好序O(N*logN)
	int *begin, *end;
	begin = a;
	end = a + length - 1;
	while (begin<end)
	{
		if (*begin + *end == sum)
		{
			cout << *begin <<" "<< *end << endl;
			//若需要输出所有的整数对,加上以下两句
			begin++;
			end--;			
		}
		else if (*begin + *end>sum)
		{
			end--;
		}
		else
		{
			begin++;
		}
	}
}

int main( )
{
	int sum = 11;//
	int max_index = 0;//最长回文子串的中心点位置
	int str[8] = {1,2,4,5,6,7,8,9};

	TwoSum(str, 8, sum);

	return 0;
}

//核心函数的替换(使用该形式的代码可以在返回两个数的同时,返回这两个数的位置)
bool find_num(int data[], unsigned int length, int sum, int& first_num, int& second_num)
{   
    if(length < 1)
        return true;

    int begin = 0;
    int end = length - 1;

    while(end > begin)
    {
        long current_sum = data[begin] + data[end];

        if(current_sum == sum)
        {
            first_num = data[begin];
            second_num = data[end];
			return true;
        }
        else if(current_sum > sum)
            end--;
        else
            begin++;
    }
    return false;
}


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值