1.两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]
解1:
- 思路:由于要求返回对应索引号,那么索引号得记录,并且与元素绑定。
- 先将元素值与索引号存入哈希表
- 再遍历哈希表,对每个元素值寻找满足target=num1+num2的另一半
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int> numsmap;
/* 将数组的值与下标存入哈希表 */
for (int i = 0; i < nums.size(); ++i){
numsmap[nums[i]] = i;
}
/* 使用map<>会默认按升序排序 */
/* 而使用unordered_map则是无序哈希表 */
for (int i = 0; i < nums.size(); ++i){
if (numsmap.find(target - nums[i]) != numsmap.end() && numsmap[target - nums[i]] != i){
/* 返回值是vector<int>时 */
return {i , numsmap[target-nums[i]]};
}
}
/* 返回值是vector<int>时 */
return {};
}
};
解2:
- 对2数求和来说,本题特殊。可以只遍历一次哈希表。速度显著提升。
- 遍历时,寻找符合条件的另一半
- 找到则返回2个索引
- 找不到则把当前元素存入哈希表,以供还未存入表的元素寻找匹配
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
/* 使用map<>会默认按升序排序 */
/* 而使用unordered_map则是无序哈希表 */
unordered_map<int,int> numsmap;
/* 将数组的值与下标存入哈希表 */
for (int i = 0; i < nums.size(); ++i){
if (numsmap.find(target - nums[i]) != numsmap.end()){
return {i , numsmap[target-nums[i]]};
}
numsmap[nums[i]] = i;
}
/* 返回值是vector<int>时 */
return {};
}
};
复杂度分析:
- 对哈希表操作
numsmap[nums[i]] = i;
的时间复杂度为O(1) - 因此时间复杂度主要来自for循环。而解1使用了2次for循环,解2才一次。那么解1的时间复杂度为O(2·N·1),解二的时间复杂度为O(N·1)
- 因为使用了哈希表,因此的空间复杂度为O(N)
167. 两数之和 II - 输入有序数组
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9 输出: [1,2] 解释: 2 与 7 之和等于目标数 9
。因此 index1 = 1, index2 = 2 。
我们可以使用 两数之和 的解法在 O(n^2)O(n
2
) 时间 O(1)O(1) 空间暴力解决,也可以用哈希表在 O(n)O(n) 时间和 O(n)O(n) 空间内解决。然而,这两种方法都没有用到输入数组已经排序的性质,我们可以做得更好。
解3:双指针法
- 有用的信息:升序数组有序排列
- 左右指针夹逼:若两数之和比目标值大,则移动右指针,减小和
- 若两数之和比目标值小,则移动左指针,增大和
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
/* 因为给定的是升序排列数组的有序数组 */
/* 而使用双指针夹逼法 */
int tmp, left = 0, right = nums.size() - 1;
while(left < right){
/* 升序数组中,右侧都是大数,左侧都是小数 */
tmp = nums[left] + nums[right];
/* 和偏小,则移动左指针,使和增大 */
if (target > tmp){
++left;
} else if (target < tmp){
--right;
} else {
/* 相等,返回一个vector */
return {++left, ++right};
}
}
/* 返回值是vector<int>时 */
return {};
}
};
复杂度分析:
- 没有for循环,但是出现了while循环,在最坏的情况下,需要循环N次,因此时间复杂度为O(N)
- 因为没有使用额外的大型数据结构,因此的空间复杂度为O(1)
15. 三数之和(难点在于去除重复解)
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]
解1:排序 + 双指针
算法流程:
-
特例判断,对于数组长度 n,如果数组为空或者数组长度小于 3,返回空
-
对数组进行升序排序。
-
遍历排序后数组:
若 nums[i]>0:因为数组是升序,所以后面不可能有三个数加和等于 0,直接返回结果。
对于重复元素:跳过,避免出现重复解 -
初始时令左指针 L=i+1,右指针 R=n−1,当 L<R 时,执行循环:
- 当 nums[i]+nums[L]+nums[R]==0,执行循环,判断左界和右界是否和下一位置重复,去除重复解。并同时将 L,RL,R 移到下一位置,寻找新的解
- 若和大于 0,说明 nums[R]太大,R 左移
- 若和小于 0,说明 nums[L]太小,L 右移
class Solution{
public:
vector<vector<int>> threeSum(vector<int>& nums){
vector<vector<int>> result;
int tmp, i;
if(nums.size() < 3)
return {};
/* 排序 */
sort(nums.begin(), nums.end());
if(nums.back() < 0 || nums.front() > 0)
return result;
/* 定义左右指针 */
int left, right;
for (i = 0; i < nums.size() - 2; ++i){
/* 相邻数相同,则只需计算前一个,后续的都跳过 */
for(; i > 0 && i < nums.size() && nums[i - 1] == nums[i] ; ++i);
/* 以下可视为两数求和 */
left = i + 1;
right = nums.size() - 1;
while(left < right){
tmp = nums[i] + nums[left] + nums[right];
if(tmp == 0) {
result.push_back({nums[i], nums[left], nums[right]});
/* 移动指针前先查看是否重复 */
while(left < right && nums[left] == nums[left + 1]) ++left;
while(left < right && nums[right] == nums[right - 1]) --right;
/* 正常移动指针 */
++left;
--right;
} else if (tmp > 0){
--right;
} else {
++left;
}
}
}
return result;
}
};
复杂度分析
时间复杂度:O(N2)
- 数组排序 O(N·logN)
- 遍历数组 O(N)
- 双指针遍历 O(N)
- 总体 O(N·logN)+O(N)·O(N)
空间复杂度:O(1)
解2:哈希表法
算法流程:
- 遍历数组,取数i,后续处理视为求取2数之和
- 再遍历数组,取数j
- 查找哈希表中,是否存在target-i-j的数
class Solution{
public:
vector<vector<int>> threeSum(vector<int>& nums){
vector<vector<int>> result;
if(nums.size() == 0)
return {};
sort(nums.begin(), nums.end()); //对数组排序是为了后续去重
/* 遍历数组 */
for(int i=0; i<nums.size(); ++i){
/* 连续2个数一样的话,跳过 */
if(i!=0 && nums[i] == nums[i-1])
continue;
/* 以下的处理可认为2数之和为0-nums[j] */
unordered_map<int, int> map;
/* 遍历数组 */
for(int j=i+1; j<nums.size(); ++j){
/* 第三个数通过查询哈希表获得 */
if(map.find(-nums[i]-nums[j]) != map.end()){
result.push_back({nums[i], nums[j], -nums[i]-nums[j]});
while(j+1 < nums.size() && nums[j] == nums[j+1])
j++;
}
/* 对每个数都存入哈希表 */
map.insert({nums[j], j});
}
}
return result;
}
};
复杂度分析
时间复杂度:O(N2)
- 数组排序 O(N·logN)
- 遍历数组 O(N)
- 遍历数组 O(N)
- 查询哈希表O(1)
- 总体 O(N·logN)+O(N)·O(N)·O(1)
空间复杂度:O(N)
16. 最接近的三数之和
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.
与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).
解1:双指针法
- 先排序,再利用双指针夹逼法
- 三数之和较大则移动右指针
- 较小则移动左指针
- 相等则返回
- 每次比较都要更新距离dis与最接近target的值result
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
int left, right, tmp, mindis = INT_MAX, result;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size() - 2; ++i){
/* 跳过重复 */
for (;i > 0 && i < nums.size() - 2 && nums[i] == nums[i - 1]; ++i);
left = i + 1, right = nums.size() - 1;
while(left < right){
/* 三数求和 */
tmp = nums[i] + nums[left] + nums[right];
/* 更新距离值 */
if (abs(target - tmp) < mindis){
mindis = abs(target - tmp);
result = tmp;
}
/* 移动左右指针 */
if(tmp > target){
--right;
} else if (tmp < target){
++left;
} else {
return target;
}
}
}
return result;
}
};
复杂度分析
时间复杂度:O(N2)
- 数组排序 O(N·logN)
- 遍历数组 O(N)
- 双指针遍历 O(N)
- 总体 O(N·logN)+O(N)·O(N)
空间复杂度:O(1)