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语句。