15.三数之和
一、题目
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
**注意:**答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
提示:
3 <= nums.length <= 3000
-105 <= nums[i] <= 105
二、解法
方法一:哈希法(非常烧脑的办法,我自己一开始没想出来)
直接给出代码,我感觉最难想的就是注释部分,有兴趣的话可以用{-2,-2,-2,-1,-1,-1,0,0,0,1,1,1,2,2,2}这个案例自己推一推,就能理解代码里为什么这么做了。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(),nums.end());
for(int i = 0; i < nums.size(); i++){
if (nums[i] > 0) {
break;
}
//当元素a前一个元素已经被处理过的时候,就跳过
if(i > 0 && nums[i] == nums[i-1]){
continue;
}
unordered_set<int> s;
for(int j = i + 1; j < nums.size(); j++){
//当元素b和前两位相等,且前两位都不是a的情况下,可以跳过
if(j>i+2 && nums[j-1]==nums[j] && nums[j-2]==nums[j-1]){
continue;
}
int c = 0-nums[i]-nums[j];
if(s.find(c) != s.end()){
result.push_back({nums[i],nums[j],c});
s.erase(c); //如果减出来的元素曾经存在于s中,就消除s里所有等于c的元素
}else{
s.insert(nums[j]);
}
}
}
return result;
}
};
方法二:双指针(推荐)
算法思路:
给定一个整数数组 nums
,我们需要找到所有满足以下条件的三元组 [nums[i], nums[j], nums[k]]
:
-
i != j
,i != k
,j != k
; -
nums[i] + nums[j] + nums[k] == 0
。
我们可以使用双指针的方法。
首先,对数组进行排序,这样可以方便后续的指针移动操作。然后,我们遍历排序后的数组,将当前元素作为三元组的第一个元素。使用两个指针,一个指向当前元素的下一个位置,另一个指向数组的最后一个位置。
在指针移动的过程中,我们计算当前三个元素的和 sum = nums[i] + nums[left] + nums[right]
:
- 如果
sum
等于零,说明找到了一个满足条件的三元组,将这个三元组添加到结果列表中。 - 如果
sum
小于零,说明当前和偏小,我们需要增加和的值,因此将左指针向右移动一位。 - 如果
sum
大于零,说明当前和偏大,我们需要减小和的值,因此将右指针向左移动一位。
内部去重操作:在移动指针时,我们需要跳过重复的元素,以避免生成重复的三元组。如果当前指针所指的元素与前一个元素相同,我们可以继续移动指针,直到找到一个不同的元素为止。
详细实现:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(), nums.end()); // 对数组进行排序
vector<vector<int>> result; // 存储结果的二维数组
for (int i = 0; i < nums.size() - 2; ++i) {
if (i > 0 && nums[i] == nums[i - 1])
continue; // 外层循环去重操作
int left = i + 1; // 左指针
int right = nums.size() - 1; // 右指针
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 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 (sum < 0) {
left++; // 增加和的值
} else {
right--; // 减小和的值
}
}
}
return result;
}
};
算法分析:
该算法的时间复杂度为 O(n^2),其中 n 是数组的长度。在算法的主要部分,我们有两个循环嵌套,其中外层循环遍历数组的每个元素,内层循环使用双指针进行遍历。由于排序的时间复杂度为 O(n log n),所以排序操作不会成为主要的时间复杂度来源。
算法的空间复杂度为 O(1),不考虑结果的空间占用。我们只使用了常数个额外变量来存储指针和结果,没有使用额外的空间。
因此,该算法在时间和空间效率上是相对较优的解决方法。
一些拓展
三数之和问题的应用
三数之和问题在实际开发中有一些应用场景。下面是其中的一些例子:
寻找数组中和为特定值的三元组:除了题目中要求的和为0的三元组,我们也可以根据实际需求,在给定一个目标和的情况下,寻找数组中和为目标和的三元组。这个应用场景可以用在财务系统中,寻找某个特定金额的账单组合,或者在数据分析中,寻找满足某个条件的数据组合等。
寻找最接近目标值的三元组:与上述类似,我们也可以寻找和最接近目标值的三元组。在这种情况下,我们需要维护一个最小的差值,并记录对应的三元组。这个应用场景可以用在股票交易系统中,寻找最接近目标股价的买卖组合等。
寻找唯一的三元组:在题目中已经要求答案中不可以包含重复的三元组,但是在实际开发中,可能需要处理一些包含重复三元组的情况。我们可以通过一些额外的判断和去重操作,来解决这个问题。
优化思路:双指针法的进一步优化
在双指针法中,我们使用两个指针来遍历数组并查找三元组。然而,在某些情况下,我们可以通过一些优化来提高算法的效率。
在排序后的数组中,如果第一个元素大于0,那么后面的元素肯定也大于0,因此不可能存在和为0的三元组,可以直接返回空结果。
当固定第一个元素后,如果第一个元素和当前的最大和最小元素之和仍小于0,那么可以直接跳过当前元素,进入下一个循环。这是因为后面的元素更大,无法凑出和为0的三元组。文章来源:https://www.toymoban.com/news/detail-538559.html
这些优化可以在算法实现中加以考虑,以提高算法的效率。文章来源地址https://www.toymoban.com/news/detail-538559.html
到了这里,关于LeetCode15.三数之和的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!