两数之和问题-2sum
问题如下。
题目描述
给定一个从小到大的数组和一个目标数 t,在其中找到两个数,使得两数之和与目标数相等,输出两个数在数组中的位置。
输入
第一行两个整数 n,t。(1 ≤ n ≤ 1000000,1 ≤ t ≤ 20000000)
接下来一行 n 个数,均小于 10000000。
输出
输出两个用空格隔开的数表示位置(从零开始计数),答案有唯一解。
样例输出
6 15 1 5 6 7 10 26
样例输出
1 4
注意的几个点:
1、数组是有序的,从小到大;
2、数字较大,需要定义在全局变量上存储(局部变量在windows下只有2M的栈区来存储函数参数值或者局部变量值等)。
1、暴力搜索
首先想到的就是暴力搜索,两层循环查找,找到直接输出。
// n是有n个数字,t是目标数。
int two_sum_search (int *num, int n, int t) {
//法1:暴力求解
cout << "暴力求解: ";
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (num[j] == t - num[i]) {
cout << i << " " << j << endl << endl;
return 0;
}
}
}
return 0;
}
但缺点也很明显,当数据量大时,很容易超时,因为时间复杂度是O(n2),空间复杂度是O(1)。
2、二分查找
由于数组有序,当确定第一个数时,第二个数字可以在剩下的数字当中进行二分查找,从而提升效率。代码如下。
int two_sum_binarysearch (int *num, int n, int t) {
//法2:有序,二分查找
cout << "二分查找: ";
for (int i = 0; i < n; i++) {
int temp = t - num[i];
int l = i + 1, r = n - 1, mid;
while (l <= r) {
mid = (l + r) / 2;
if (num[mid] == temp) {
cout << i << " " << mid << endl << endl;
return 0;
}
if (num[mid] > temp) {
r = mid - 1;
} else {
l = mid + 1;
}
}
}
return 0;
}
经过二分查找后,时间复杂度优化为O(nlogn),因为二分查找的复杂度为O(logn),空间空间复杂度O(1)。
3、哈希方法(标记数组)
通过标记数组mark来记录每个数字出现的序号。比如num[]={1, 5, 7, 9, 10, 25}
,通过mark
数组标记之后,mark[1] = 0,mark[5] = 1,mark[7] = 2,mark[9] = 3,mark[10] = 4,mark[25] = 5
,也就是mark
的大小就是值的最大范围,而mark
里面的值对应着num
的序号,也就是mark[num[i]] = i
。
这样通过确定一个数字num[i]
,另一个数字t-num[i]
的值,通过查看mark[t - num[i]]
是否不为0就获得了。具体代码如下。
int two_sum_hash(int *num, int n, int t) {
// 法3:哈希方法:标记数组
cout << "哈希法-标记数组: ";
for (int i = 0; i < n; i++) {
int temp = t - num[i];
if (mark[temp]) { // temp对应的下标出现过,不等于0
cout << mark[temp] << " " << i << endl << endl;
break;
}
mark[num[i]] = i;
}
return 0;
}
经过mark标记后,时间复杂度是O(n)了,只遍历了一遍数组。空间复杂度由于有了mark数组,变为了O(n)。
4、双指针法
比如有数组num[]={1, 5, 7, 9, 10, 25}
,,需要获得和为15的数字序号。设立2个指针,头指针p指向开头0
处,尾指针q指向末尾n-1
处,取值进行相加得到26,26大于15,于是移动尾指针向前q--
,得到1+10=11,小于15,于是移动头指针p++
,得到5+10=15,即得到结果。当指针错开了或重合了认为没用找到答案。
代码如下。
int two_sum_doubleP(int *num, int n, int t) {
// 法4:双指针法
cout << "双指针法: " ;
int l = 0, r = n - 1;
while(l <= r) {
if (num[l] + num[r] == t) {
cout << l << " " << r << endl << endl;
break;
}
if (num[l] + num[r] > t) {
r--;
} else {
l++;
}
}
return 0;
}
该方法在时间复杂度上也是O(n),空间复杂度为*O(1)*了,针对有序数组,该方法是效率最高的方法。
总结
针对两数之和问题,有有序数组和无序数组之分。
有序数组 | 有序数组 | 无序数组 | 无序数组 | |
---|---|---|---|---|
时间复杂度 | 空间复杂度 | 时间复杂度 | 空间复杂度 | |
暴力搜索 | O(n2) | O(1) | O(n2) | O(1) |
二分查找 | O(nlogn) | O(1) | O(nlogn) (先排序-快排:O(logn)) | O(1) |
hash法 | O(n) | O(n) | O(n) | O(n) |
双指针法 | O(n) | O(1) | O(nlogn)(先排序O(logn)) | O(1) |
有序数组最优效率方法是双指针法,时间效率最低,空间几乎没有。针对无序数组,在空间限制的情况下,也可用双指针法(空间复杂度O(1));要是不限定空间,可以用hash法,时间空间都是O(n),时间复杂度低。
完整代码:
#include <iostream>
using namespace std;
int n, t;
int num[1000005] = {0};
int mark[20000005] = {0};
int two_sum_search (int *num, int n, int t) {
//法1:暴力求解
cout << "暴力求解: ";
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (num[j] == t - num[i]) {
cout << i << " " << j << endl << endl;
return 0;
}
}
}
return 0;
}
int two_sum_binarysearch (int *num, int n, int t) {
//法2:有序,二分查找
cout << "二分查找: ";
for (int i = 0; i < n; i++) {
int temp = t - num[i];
int l = i + 1, r = n - 1, mid;
while (l <= r) {
mid = (l + r) / 2;
if (num[mid] == temp) {
cout << i << " " << mid << endl << endl;
return 0;
}
if (num[mid] > temp) {
r = mid - 1;
} else {
l = mid + 1;
}
}
}
return 0;
}
int two_sum_hash(int *num, int n, int t) {
// 法3:哈希方法:标记数组
cout << "哈希法-标记数组: ";
for (int i = 0; i < n; i++) {
int temp = t - num[i];
if (mark[temp]) { // temp对应的下标出现过,不等于0
cout << mark[temp] << " " << i << endl << endl;
break;
}
mark[num[i]] = i;
}
return 0;
}
int two_sum_doubleP(int *num, int n, int t) {
// 法4:双指针法
cout << "双指针法: " ;
int l = 0, r = n - 1;
while(l <= r) {
if (num[l] + num[r] == t) {
cout << l << " " << r << endl << endl;
break;
}
if (num[l] + num[r] > t) {
r--;
} else {
l++;
}
}
return 0;
}
int main() {
cin >> n >> t;
for (int i = 0; i < n; i++) {
cin >> num[i];
}
//法1:暴力求解
two_sum_search (num, n, t);
//法2:有序,二分查找
two_sum_binarysearch (num, n, t);
// 法3:哈希方法:标记数组
two_sum_hash(num, n, t);
// 法4:双指针法
two_sum_doubleP(num, n, t);
return 0;
}
输出如下:
6 15
1 2 3 6 9 111
暴力求解: 3 4二分查找: 3 4
哈希法-标记数组: 3 4
双指针法: 3 4