这同样是博主在面试 Microsoft 时遇到的面试题,只是当时博主没有答好,惭愧...
问题描述很简单,给出一个 int 类型的数组,能否快速找出数组中的两个数字,使它们的和等于一个给定的值
即,给出数组
假定给定两数只和 SUM 为15
那么我们需要找到的分别是 (8, 7),(4, 11),(9, 6)
首先当我被问到这个问题的时候,我首先想到的就是穷举法,即遍历整个数组的所有可能的组合,穷举法实现简单,只需要两个 for 循环便可得到我们想要的答案
面试官又问我该算法的时间复杂度,显然其时间复杂度为N*(N - 1) / 2,即O(N2)
接着被问到能不能想个更加高效的算法来实现该功能,也就是说要我优化其时间复杂度。想到“时间复杂度”,首先想到的自然是数据结构的内容,由于之前那个先入为主的观念,
我思考的方向始终是逐个匹配的算法
以下是我当时的想法:
当时我首先想到的是先把数组做排序,记录下第一个数值小于 SUM 的元素a[n],接着用a[0]开始与a[n]求和,如果其结果大于 SUM,则计算a[0]与a[n-1]的值并与 SUM 进行比较,
如果其结果小于 SUM,则计算a[1]与a[n]的值并与 SUM 进行比较。
当时的面试管朝我笑笑便闲聊了几句离开了,前后才20分钟,感觉不妙。后来博主想想,觉得这算法的时间复杂度的确已经做到了优化,但似乎任然不到位!
**后来经过多方查阅资料,终于找到一种最为有效的方法,在此与大家分享:
首先我们得转变思路,既然我们要验证数组中是否存在一个数与arr[i]之和为 SUM,我们何不判别 SUM - arr[i] 是否在数组中呢。这样,该问题就变成了一个查找的算法。但是,直接在一个无序数组中查找一个数的时间复杂度是O(N),那么对于每个arr[i]都需要查找对应的SUM - arr[i],最终的时间复杂度仍然是O(N2),所以,新排序是正确的。在已排好序的数组中进行二分查找的时间复杂度为O(log2N) ,对长度为N的数组进行排序本身也需要O(Nlog2N)的时间,所以总的时间复杂度仍然是O(Nlog2N)。
--------------------附录--------------------
该方法所用到的算法
快速排序:
//快速排序
int *qsort(int *a, int al, int ar)
{
int i = al, j = ar; //al, ar分别为子数组的首尾索引值
int k = a[al]; //保存key值
while (i < j && a[j] >= k)
{
j--;
}
if (i < j)
{
a[i] = a[j];
}
while (i < j && a[i] < k)
{
i++;
}
if(i < j)
{
a[j] = a[i];
}
a[i] = k;
if((i - al) > 0)
{
qsort(a, al, i-1);
}
if ((ar-i) > 0)
{
qsort(a, i+1, ar);
}
return a;
}
二分查找:
//二分查找
void search(int *a, int L, int s)
{
cout<<"查找满足和为"<<s<<"的两个数..."<<endl;
int cnt = 0;
for (int c = 0; c <= L - 1; c++)
{
int i = c, j = L -1, d = s - a[i];
while(i <= j)
{
if (a[mid] == d && j != c)
{
cnt++;
cout<<"第"<<cnt<<"组:"<<a[c]<<" "<<a[mid]<<endl;
break;
}
else if (a[mid] > d)
{
j = mid - 1;
}
else
{
i = mid +1;
}
}
}
}
----------运行结果----------