两数之和问题-2sum

两数之和问题-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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值