问题:输入一个已经按升序排序过的数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字。
要求时间复杂度是O(n)。如果有多对数字的和等于输入的数字M,输出任意一对即可。例如输入数组1、2、4、7、11、15和数字15。由于4+11=15,因此输出4和11。
一般思路:
(1)让指针指向数组的头部和尾部,相加,如果小于M,则增大头指针,如果大于则减小尾指针
(2)退出的条件,相等或者头部=尾部
代码如下:
void function(int a[],int n,int M)
{
int i=0,j=n-1;
while(i!=j){
if(a[i]+a[j]==M){
printf("%d,%d",a[i],a[j]);
break;
}
a[i]+a[j]>M?j--:i++;
}
}
推广:
如果数组中的数是无序的,如何在O(n)的时间复杂度内,找出这两个数。
分析:关于算法,在笔者看来,工程中要一个问题的复杂度可以分为三个部分:时间复杂度,空间复杂度,逻辑复杂度。在这三种类型的复杂度中,要降低任何一种复杂度都可以通过牺牲另外两个复杂度获得。时间复杂度和空间复杂度相信大家都不陌生,逻辑复杂度主要包括代码的逻辑性和所涉及的数据结构,比如排序,可以用线性的数据结构实现,也可以用树型的数据结构实现,由于树性结构比线性结构逻辑性要强,所以我们可以通过平衡二叉树,红黑树降低排序某个方面时间复杂度。
本题思路:
以牺牲空间复杂度来降低时间复杂度。所谓牺牲空间复杂度,就是要开辟一个内容空间来保存某些对于求解有帮助的信息。正如在动态规划中,牺牲空间复杂度就是开辟空间来保存重叠子问题的解。回到本题中,要求时间复杂度为O(n),即每个数组元素只遍历一遍,如何在遍历到4的时候判定是否存在11,让两个数的和为15呢?那么就需要保存数组中是否有11这个数,这就是我们牺牲空间复杂度所要保存的东西。要保存是否存在某个数,我们很自然就想到散列表,下面就具体说一下如何用空间复杂度换取时间复杂度。
同样输入数组2、1、4、11、7、15和数字15。
从数字15入手,两个数的和为15,共有多少组呢?0和15,1和14,2和13,…,7和8。定义大小为15/2+1=8的Hash表T统计某个数出现的次数,现在要完成的就是如何将0和15,1和14,2和13,…,7和8通过Hash函数映射到同位置。如a如何通过Hash函数映射,最简单的方法,如果a小于等于15/2=7,则Hash(a)=a;如果a大于15/2=7,则Hash(a)=15-a。
代码如下:
void function(int a[],int n,int M)
{
int hash[M/2+1]={0};
for(int i=0,i<n;i++)
{
if(a[i]<=M/2)
{
if(1==hash(a[i]))
printf("%d,%d \n",a[i],a[15-i]);
}
if(a[i]>M/2&&a[i]<=M)
{
if(1==hash(a[15-i]))
printf("%d,%d \n",a[i],a[15-i]);
}
}
}