题目描述
设计一个算法,找出数组中两数之和为指定值的所有整数对。一个数只能属于一个数对。
题目传送门
示例 1:
输入: nums = [5,6,5], target = 11
输出: [[5,6]]
示例 2:
输入: nums = [5,6,5,6], target = 11
输出: [[5,6],[5,6]]
提示:
- nums.length <= 100000
- -10^5 <= nums[i], target <= 10^5
解题思路与代码
- 这道题属于一道比较简单的题了。我们可以用很多种方法去做它。比如说排序 + 双指针,或者说用哈希映射实现快速查找?但是唯一可能不行的就是暴力破解。那样可能时间复杂度不过关,hh
那么就让我来一一介绍这些可行的方法吧!
方案一:哈希法实现快速查找
- 对于这种方法,我们首先建立了数组中元素与元素对应的个数的哈希映射,我们用
unordered_map<int,int> map;
去实现。 - 之后呢,我们再去遍历数组找到对应的元素,然后把他们在map中对应的元素个数减去。并存到结果集中
- 最后返回结果集即可
具体代码如下:
class Solution {
public:
vector<vector<int>> pairSums(vector<int>& nums, int target) {
unordered_map<int,int> map;
for(int& num : nums)
++map[num];
vector<vector<int>> result;
for(int i = 0; i < nums.size(); ++i){
int n1 = nums[i];
if(map[n1]){
int n2 = target - n1;
--map[n1];
if(map[n2]){
result.push_back({n1,n2});
--map[n2];
}else{
++map[n1];
}
}
}
return result;
}
};
复杂度分析
时间复杂度:
- 该算法的时间复杂度主要取决于两个循环。第一个循环用于填充映射,第二个循环用于找到和为目标值的整数对。因为这两个循环都是线性的,所以总的时间复杂度是O(n) + O(n) = O(2n)。但在大O表示法中,我们通常忽略掉常数因子,所以最终的时间复杂度是O(n)。
空间复杂度:
- 这个算法的空间复杂度主要取决于用于存储输入数组元素和结果的空间。数组的空间占用是O(n),而结果的空间占用最多也是O(n),因为最多只能有n/2个有效的对。因此,总的空间复杂度是O(n) + O(n) = O(2n)。同样,我们在大O表示法中通常忽略掉常数因子,所以最终的空间复杂度是O(n)。
方案二:优化!边遍历数组,边建立哈希映射
-
就如我们的方案题目说的一样,我们边遍历数组,边建立哈希映射。
-
在for循环的途中,我们去map中寻找有没有target - nums[i] 的键,如果有,并且对于的值 > 0。我们就可以把它们加入结果集中,并且对键对应的值 - 1。
-
如果在map中找不到对于的键,我们就把nums[i] 加入map中,并 ++ 它的value。
-
这样遍历完一次数组,我们也就找到了自己的答案。
-
相比于方案一,节省了一倍的遍历时间。
具体的代码如下:
class Solution {
public:
vector<vector<int>> pairSums(vector<int>& nums, int target) {
unordered_map<int,int> map;
vector<vector<int>> result;
for(int& num : nums){
auto it = map.find(target - num);
if(it != map.end() && it->second > 0){
result.push_back({it->first,num});
--it->second;
}else ++map[num];
}
return result;
}
};
复杂度分析
时间复杂度:
- 这段代码的时间复杂度主要由循环决定。对于数组中的每个元素,算法都会在哈希表中进行查找,这是一个常数时间操作(平均情况下)。因此,总的时间复杂度为O(n)。
空间复杂度:
- 该算法的空间复杂度主要取决于用于存储输入数组元素和结果的空间。哈希表的空间占用是O(n),结果的空间占用最多也是O(n),因为最多只能有n/2个有效的对。因此,总的空间复杂度是O(n) + O(n) = O(2n)。同样,在大O表示法中,我们通常忽略掉常数因子,所以最终的空间复杂度是O(n)。
因此,相比第一段代码,这段代码在时间复杂度上有所优化,因为它将两个操作合并到了一个循环中,但空间复杂度保持不变。
方案三:排序 + 双指针
-
这种方法也很简单。我们先把数组排序。然后创建两个指针,一个指向数组头元素,一个指向数组尾元素。
-
之后用while循环来遍历数组,如果前后之和 > target 则right-- ,如果前后之和 < target ,则left ++ , 否则就等于是找到了,把它们添加进结果集中, ++left --right
-
最后返回结果集即可
具体代码如下:
class Solution {
public:
vector<vector<int>> pairSums(vector<int>& nums, int target) {
vector<vector<int>> result;
sort(nums.begin(),nums.end());
int left = 0;
int right = nums.size() -1;
while(left < right){
if(nums[left] + nums[right] > target) --right;
else if(nums[left] + nums[right] < target) ++left;
else{
result.push_back({nums[left],nums[right]});
++left;
--right;
}
}
return result;
}
};
复杂度分析
时间复杂度:
- 这段代码的时间复杂度由排序操作和双指针扫描决定。在最坏的情况下,使用快速排序或归并排序对数组进行排序的时间复杂度为O(n log n)。双指针扫描数组的时间复杂度为O(n)。因此,总的时间复杂度是O(n log n) + O(n)。在大O表示法中,我们取最高阶项,所以最终的时间复杂度是O(n log n)。
空间复杂度:
- 这段代码的空间复杂度主要取决于用于存储输入数组和结果的空间。数组的空间占用是O(n),结果的空间占用最多也是O(n),因为最多只能有n/2个有效的对。排序操作通常需要O(log n)的额外空间(例如在归并排序中)。因此,总的空间复杂度是O(n) + O(n) + O(log n) = O(2n + log n)。在大O表示法中,我们取最高阶项,所以最终的空间复杂度是O(n)。
这段代码的主要优点是它没有使用额外的哈希表来存储元素的频率,因此可能在空间利用率上稍微优于前两段代码。然而,它的时间复杂度比前两段代码高,因为它需要对数组进行排序。
总结
这道题目是一道经典的算法设计题,主要考察了以下几个方面:
-
对哈希表数据结构的理解和应用:哈希表是一种常用的数据结构,它可以用于快速查找元素,是许多算法问题的关键。在这道题目中,哈希表被用来记录数组中的元素及其出现次数。
-
对双指针技术的理解和应用:双指针是一种常用的遍历技术,可以在遍历数组或链表时提供额外的信息。在这道题目中,双指针被用来在有序数组中寻找满足特定条件的元素对。
-
对排序算法的理解和应用:如果采用双指针的解法,你需要首先对数组进行排序。排序是编程中非常常见的任务,对其理解和掌握是非常重要的。
-
思维的灵活性:这道题目可以有多种解法,考察了你对问题的理解和分析能力,以及在给定约束条件下寻找最优解的能力。
-
代码的编写和调试能力:不仅要设计出算法,还要将算法转化为正确且高效的代码,这也是一项非常重要的能力。
在实际的软件开发中,类似的问题也经常会遇到。例如,在一些推荐系统中,可能需要寻找满足某些条件的元素对,这时候就可以借鉴这道题目的解法。
最后的最后,如果你觉得我的这篇文章写的不错的话,请给我一个赞与收藏,关注我,我会继续给大家带来更多更优质的干货内容
。