N Sum问题总结——2 Sum问题

K Sum问题是指,在一个大小为n的数组中,找出k个数相加的和等于给定的数,这个叫做K Sum问题。
LeetCode上的K Sum问题包括
1. 两数之和
15. 三数之和
16. 最接近的三数之和
18. 四数之和
167. 两数之和 II – 输入有序数组
454. 四数相加 II

简单思路

  • 暴力遍历。枚举数组所有大小为N的子集,时间复杂度为O(nK)。
  • 当K=1时,相当于在数组中寻找特定值。当K=a时,固定一个值,问题转化为K=a-1情况下的子问题。

K = 1

当N=1时,问题简化为在一个数组内寻找一个数。可使用简单的遍历一遍,或排序后二分查找,代码不再赘述。

2 Sum问题

最常见的就是2 Sum问题,这是一个经典问题,思路如下:

思路

  • 可用排序方法时。
    (1)按升序排序,问题变成在有序数组中寻找两数和
    (2) 双指针,ptr1指向数组头,ptr2指向数组尾。
    (3)若指针所指的2数之和 大于 target number,ptr2向队首移动,直至 其和不大于 target number。
    (4)若指针所指的2数之和 小于 target number,ptr1向队尾移动,直至 其和不小于 target number。
    (5)判断2数之和与 target number 是否相等。
    若相等,2个指针所指的数即为结果。
    若不等,当ptr1仍处于ptr2前时,重复(3)(4)(5),否则,返回错误报告。
  • 不可排序时。建立一个 map 或 hash_map。边遍历数组边判断 target number – nums[i] 是否在map中。

比较

第一种思路的时间复杂度主要由排序所需时间决定,总时间复杂度为O(n logn)。
第二种思路的主要目的是以空间换时间,时间复杂度为O(n),而空间复杂度为O(n)。

代码片段

第一种思路(对排序后的数组):

vector<int> twoSum(vector<int>& numbers, int target) {
    auto ptr1 = numbers.begin();
    auto ptr2 = numbers.end()-1;
    while(ptr1 < ptr2){
        while(*ptr1 + *ptr2 > target)
            --ptr2;
        while(*ptr1 + *ptr2 < target)
            ++ptr1;
        if(*ptr1 + *ptr2 == target)
            return {ptr1-numbers.begin()+1,ptr2-numbers.begin()+1};
    }
}

代码解释:第5行与第7行的和与 target number 时,应用while语句而不是if语句,可减少外层while循环次数。

第二种思路:

vector<int> twoSum(vector<int>& nums, int target) {
	map<int, int> tmp;
	for (int i = 0; i < nums.size(); ++i) {
		if (tmp.count(target - nums[i])) {   
            return { i, tmp[target - nums[i]] };
		}
        tmp.insert(make_pair(nums[i],i));
	}
}

代码分析:第7行代码 tmp.insert(make_pair(nums[i],i)); 。因为思路是边扫描边存储,所以此处均为map容器的插入操作。考虑一个问题:为什么这里不用tmp[nums[i]] = i; 语句?
答:tmp[nums[i]] = i; 语句也可以达到同样的目的(虽然会出人意料,但这就是使用下标操作的副作用:如果关键字还未在map中,下标操作会插入一个具有给点关键字的元素。这也是为什么第3行使用count或find判断而不是下标操作的原因。)。
然而,根据Effective STL中的分析:

当效率至关重要时,你应该在map::operator[ ]和map::insert之间仔细做出选择。如果要更新一个已有的映射表元素,则应该优先选择operator[ ];但如果要添加一个新的元素,那么最好还是选择insert。

为了提高效率,我们这里使用map::insert语句。

点赞