题目描述
给你一个整数数组 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 。
解题思路
1. 初步分析
要找三元组,最原始最暴力的方法就是三层遍历,时间复杂度 O(n^3)。但就像双指针可以把二维遍历降为一维遍历一样,使用双指针也可以把三维遍历降为二维的。
2. 三指针解法
基本思路:
- 使用三个指针 i、j、k,i 指针指示三元组的第一个数,j 指针和 k 指针相向移动,分别指向三元组的第二个数和第三个数。
- 对 nums[i]、nums[j]、nums[k] 求和,根据 sum 值大小进行讨论
- 等于 0:放入结果数组,j、k 同时移动
- 小于 0:移动 j
- 大于 0:移动 k
去重逻辑:
-
i 位去重
//检查下标合法 && 当前i位与前一个i位(nums[i-1])相等 if(i > 0 && nums[i] == nums[i - 1]){ continue; }
-
j 位去重
//检查下标合法 && 当前j位与前一个j位(nums[j-1])相等 if(j > i + 1 && nums[j] == nums[j - 1]){ ++j; continue; }
-
k 位去重(k 是从右向左移动的)
//检查下标合法 && 当前k位与前一个k位(nums[k+1])相等 if(k < nums.size() - 1 && nums[k] == nums[k + 1]){ --k; continue; }
**剪枝逻辑:**核心算法前对数组进行排序,若三元组第三位(即 k 位)元素大于 0,则可跳出当前双指针循环。
3. 代码主干
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
//遍历i位
for(int i = 0; i < nums.size() - 2; ++i){
//对i为进行去重
//定义j、k指针
int j = i + 1, k = nums.size() - 1;
while(j < k){ //利用双指针降维
//剪枝
//对j位进行去重
//对k位进行去重
//计算sum值,并对sum值进行讨论
}
}
return result;
}
完整代码
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
for(int i = 0; i < nums.size() - 2; ++i){
//对i为进行去重
if(i > 0 && nums[i] == nums[i - 1]){
continue;
}
int j = i + 1, k = nums.size() - 1;
while(j < k){ //利用双指针降维
//剪枝
if(nums[k] < 0)
break;
//对j位进行去重
if(j > i + 1 && nums[j] == nums[j - 1]){
++j;
continue;
}
//对k位进行去重
if(k < nums.size() - 1 && nums[k] == nums[k + 1]){
--k;
continue;
}
//计算sum值,并对sum值进行讨论
int sum = nums[i] + nums[j] + nums[k];
if(sum == 0){
vector<int> tmp = {nums[i], nums[j], nums[k]};
result.push_back(tmp);
++j, --k;
}else if(sum < 0){
++j;
}else if(sum > 0){
--k;
}
}
}
return result;
}
关于 unordered_set 的补充
在尝试优化时浅浅使用了一下 unordered_set<vector<int>>
,编译报错:
Line 6: Char 36: error: call to implicitly-deleted default constructor of 'unordered_set<vector<int>>'
unordered_set<vector<int>> result;
搜索了一下 unordered_set 的使用方法,发现 unordered_set 容器的类模板定义如下:
template < class Key, //容器中存储元素的类型
class Hash = hash<Key>, //确定元素存储位置所用的哈希函数
class Pred = equal_to<Key>, //判断各个元素是否相等所用的函数
class Alloc = allocator<Key> //指定分配器对象的类型
> class unordered_set;
逐个分析:
- **容器中存储元素的类型:**在 unordered_set 中,key 和 value 的类型都是一致的,指的就是 key 的类型
- **确定元素存储位置所用的哈希函数:**指定元素存储位置的哈希函数,注意:默认哈希函数 hash<Key> 只适用于基本数据类型(包括 string 类型),而不适用于自定义的结构体或者类。比如说 vector<int> 不属于基本数据类型。
- **判断各个元素是否相等所用的函数:**unordered_set 容器内部不能存储相等的元素,而衡量 2 个元素是否相等的标准,取决于该参数指定的函数。 默认情况下,使用 STL 标准库中提供的 equal_to<key> 规则,该规则仅支持可直接用 == 运算符做比较的数据类型。
- 指定分配器对象的类型:略