我是靠谱客的博主 负责小蚂蚁,最近开发中收集的这篇文章主要介绍【LeetCode】题解 - nSumTarget 问题,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

从帅张星球,认识的 labuladong ,东哥曾带领着我们一众近 300 人,进行了为期 21 天的算法挑战,虽艰难,但始终坚持下来了(毕竟是立了军令状,而且与有关)。最近开始坚持系统学算法,重新学习了 labuladong 的 nSumTarget 模板,很受启发,尤其是模板思维。

无论是最近组织【九日集训】的英雄哪里出来,还是曾经 21 天算法挑战营的主理人 labuladong,都是前辈巨人。总能给我们的成长以加持,也总希望算法这条路,后来者居上~ 。

本文,是我学习 labuladong 公众号文章 - 《一个函数秒杀 2Sum 3Sum 4Sum 问题》之后,根据自己的理解整理而来,大家可以直接点击链接中的文章,去学习,相信看完都会忍不住 加关注的。


1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

数据提示:

  • 2 <= nums.length <= 104
  • -10^9 <= nums[i] <= 10^9
  • -10^9 <= target <= 10^9
  • 只会存在一个有效答案

进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗?


解法1:双层循环 O(N^2)

略。

解法2:排序 + 左右指针 O(N) + O(LogN)

使用映射或两个数组,将索引和元素值一起排序,保证索引和元素值的对应关系和顺序,元素值数组 - 左右指针,查找和为 target 的元素值对,返回下标,在索引值数组中,找到初始时,对应下标,返回即可。

解法3:查找表 - 哈希表 O(N)

  • 在哈希表中查找 target - nums[i],存在就返回索引对,不存在就放入
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
// 构建 record 
unordered_map<int, int> record;
for(int i = 0; i < nums.size(); i++) {
auto it = record.find(target - nums[i]); // 记录当前值
if(it != record.end()) {
// 找到就直接返回
return {it->second, i};
}
record[nums[i]] = i;
}
return {};
}
};

167. 两数之和 II - 输入有序数组

给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列(升序) ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1]numbers[index2] ,则 1 <= index1 < index2 <= numbers.length

以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1index2

每个输入只对应唯一的答案 ,而且不可以重复使用相同的元素。解决方案必须只使用常量级的额外空间

示例 1:

输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:27 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2]

示例 2:

输入:numbers = [2,3,4], target = 6
输出:[1,3]
解释:24 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3]

数据提示:

  • 2 <= numbers.length <= 3 * 10^4
  • -1000 <= numbers[i] <= 1000
  • -1000 <= target <= 1000
  • 只会存在一个有效答案

前文中 1. 两数之和 的解法 1、3 依然有效。但 不满足 常量级辅助空间 的要求。唯解法 2 可满足,考虑到本题中数组有序答案唯一,直接上指针对撞即可,不用考虑诸如 多个解、重复解 等问题。

class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
// 暴力枚举,TIMEOUT →
未充分利用数组有序的特点
// 解法2:Two pointers O(N)
// 注意:返回结果中,索引是从 1 开始的
int N = numbers.size();
int lo = 0, hi = N -1;
while(lo < hi) {
int sum = numbers[lo] + numbers[hi];
if(sum > target) {
hi --;
} else if(sum < target) {
lo ++;
} else {
return {lo + 1, hi + 1};
}
}
throw std::invalid_argument("The input has no solution");
}
};

按照 labuladong 文章的递进顺序,魔改本题:nums 无序,满足和为 target 的解有多个,返回所有的解。并为了向后兼容 三数之和,新的 twoSum 函数签名如下:

vector<vector<int>> twoSumTarget(vector<int>& nums, int startIndex, int target)

完整代码如下:

// 数组传入之前 排序,避免后续 三数之和 问题,在循环中,调用本方法,导致 n 次排序操作
vector<vector<int>> twoSumTarget(vector<int>& nums, int startIndex, int target) {
int lo = startIndex, hi = nums.size() - 1;
vector<vector<int>> res;
// 保存所有和为 target 的元素对
while(lo < hi) {
int left = nums[lo], right = nums[hi];
int sum = left + right;
if(sum < target) {
// lo ++。同时跳过所有重复的 nums[lo]
while(lo < hi && nums[lo] == left) lo ++;
} else if(sum > target) {
// hi --。同时跳过所有重复的 nums[hi]
while(lo < hi && nums[hi] == right) hi --;
} else {
// 元素对保存
res.push_back({left, right});
while(lo < hi && nums[lo] == left) lo ++;
while(lo < hi && nums[hi] == right) hi --;
}
}
return res;
}

那么,167. 两数之和 II 的解法为:

class Solution {
public:
vector<vector<int>> twoSumTarget(vector<int>& nums, int startIndex, int target) {
int lo = startIndex, hi = nums.size() - 1;
vector<vector<int>> res;
// 保存所有和为 target 的元素对
while(lo < hi) {
// code ...
}
return res;
}
vector<int> twoSum(vector<int>& numbers, int target) {
// nums 有序,解唯一
return twoSumTarget(nums, 0, target)[0];
}
};

15. 三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

示例 2:

输入:nums = [0]
输出:[]

数据提示:

  • 0 <= nums.length <= 3000
  • -10^5 <= nums[i] <= 10^5

话接上文,将 三数之和问题,拆解成寻找第一个数 nums[i] 和 剩余两数之和 ltarget - nums[i]解 的问题。此题 target 为 0,我们保留 target 参数,让题解更通用。

// 数组传入之前 排序,避免枚举第一个数时,多次调用本方法,导致 n 次排序操作
vector<vector<int>> threeSumTarget(vector<int>& nums, int startIndex, int target) {
int N = nums.size();
vector<vector<int>> res;
// 穷举 threeSum 的第一个数
for(int i = startIndex; i < N; i++) {
// 对 target - nums[i] 计算 twoSum
vector<vector<int>> tuples = twoSumTarget(nums, i + 1, target - nums[i]);
for(auto& tup : tuples) {
tup.push_back(nums[i]);
// 将满足的 nums[i] push_back 构成 三元组
res.push_back(tup);
// 将三元组 push_back 到 res 中
}
// 跳过第一个元素的重复
while(i < N - 1 && nums[i] == nums[i+1]) i ++;
}
return res;
}

那么,三数之和问题的解,如下:

class Solution {
public:
vector<vector<int>> twoSumTarget(vector<int>& nums, int startIndex, int target) {
int lo = startIndex, hi = nums.size() - 1;
vector<vector<int>> res;
// 保存所有和为 target 的元素对
while(lo < hi) {
// code ...
}
return res;
}
// 数组传入之前 排序,避免枚举第一个数时,多次调用本方法,导致 n 次排序操作
vector<vector<int>> threeSumTarget(vector<int>& nums, int startIndex, int target) {
int N = nums.size();
vector<vector<int>> res;
// 穷举 threeSum 的第一个数
for(int i = startIndex; i < N; i++) {
// code ...
}
return res;
}
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(), nums.end());
return threeSumTarget(nums, 0, 0);
}
};

18. 四数之和

将 四数之和问题,拆解成寻找第一个数 nums[i] 和 剩余三数之和 ltarget - nums[i]解 的问题。
将 三数之和问题,拆解成寻找第一个数 nums[i] 和 剩余两数之和 ltarget - nums[i]解 的问题。
两数之和问题,已求解。

OK,见到递归,归纳如下:

  1. (n-1)Sum 问题已求解时,nSum 问题拆解成寻找第一个数 nums[i] 和 剩余(n-1) 数之和 target - nums[i] 解 的问题;
  2. n = 2 时,问题已求解。

nSumTarget 函数签名如下:
vector<vector<int>> nSumTarget(vector<int>& nums, int n, int startIndex, int target)

  • nums:已排序的数组;
  • n: 组成和的数字个数。fourSum,n 为 4;
  • startIndex:左侧索引位(左右指针 lo 的初始值);
  • target:和;

完整代码如下:

vector<vector<int>> nSumTarget(vector<int>& nums, int n, int startIndex, int target) {
// 将 nums 排序的操作 放在外面,避免寻找第一个数字之时,n 次的 sort 操作
assert(is_sorted(nums.begin(), nums.end()));
int sz = nums.size();
vector<vector<int>> res;
// 如果组成 target 的元素个数 n 小于 2 或者 nums 元素不足 n 个,直接返回
if(n < 2 || sz < n) return res;
if(n == 2) { // 递归的归处,最小待解决的问题 base case → twoSum 
int lo = startIndex, hi = sz - 1;
while(lo < hi) {
int left = nums[lo], right = nums[hi];
int sum = left + right;
if(sum > target) {
while(lo < hi && nums[hi] == right) hi --;
} else if(sum < target) {
while(lo < hi && nums[lo] == left) lo ++;
} else {
// 满足的数对,添加到 res 中
res.push_back( {left, right} );
while(lo < hi && nums[hi] == right) hi --;
while(lo < hi && nums[lo] == left) lo ++;
}
}
} else {
// n > 2时,返回 (n-1)Sum 的结果
for(int i = startIndex; i < sz; i++) {
vector<vector<int>> sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]);
for(auto& arr : sub) {
arr.push_back(nums[i]);
res.push_back(arr);
}
// nums[i] 重复问题
while(i < sz - 1 && nums[i] == nums[i+1]) i++;
}
}
return res;
}

由此,四数之和问题的解,如下:

class Solution {
public:
// 传入的 nums 必须有序
vector<vector<int>> nSumTarget(vector<int>& nums, int n, int startIndex, int target) {
// code ...
}
vector<vector<int>> fourSum(vector<int>& nums, int target) {
// 数组排序
sort(nums.begin(), nums.end());
return nSumTarget(nums, 4, 0, target);
}
};

参考文章

以上是我学习后的梳理,欢迎大家阅读 labuladong 公众号原文 - 《一个函数秒杀 2Sum 3Sum 4Sum 问题》。

文章信息量可能比较大,一定要对 两数之和 - 左右指针 问题有所了解之后,亲自下场写文章中的代码,且加上注释,才能有很好的理解。

当然,这篇文章一开始也是在我收藏夹中吃灰了很久,才重新阅读的。重新出发,要感谢 英雄哪里出来 的九日集训。让我坚持下来~

为其 16 轮的【九日集训】已经结束了,英雄大哥带着大家继续出发的地方在这儿,感兴趣的看一眼。

英雄哪里出来 - 自律学习群,一下午没看,就会 500 + 信息。让自律更卷。

在这里插入图片描述

最后

以上就是负责小蚂蚁为你收集整理的【LeetCode】题解 - nSumTarget 问题的全部内容,希望文章能够帮你解决【LeetCode】题解 - nSumTarget 问题所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(38)

评论列表共有 0 条评论

立即
投稿
返回
顶部