文章目录
- 题目描述
- 题目链接
- 题目难度——中等
- 方法一:暴力(参考)
- 代码/Python 参考
- 方法二:哈希
- 代码/Python参考
- 方法三:排序+双指针
- 代码/Python
- 代码/C++
- 总结
题目描述
龙门阵:这个n个数之和是不是有什么深意啊,好几个这种题,这个题虽然标的是中等,但我咋觉得考思维考得像困难一样。这个题跟两数之和还有点想,没看过两数之和的伙伴可以看我这篇LeetCode题目笔记——1.两数之和
前两种方法都会超时,仅供参考和自己记录,小伙伴可以跳过,直接到方法三。
给你一个整数数组 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
题目链接
题目难度——中等
方法一:暴力(参考)
根据提示,数据的长度最多也只有3000,所以我们可以尝试暴力一下,但是注意,虽然理论上可以暴力,但是三重循环的话,最坏情况下将会执行30003 = 2.7 * 1010 次比较,同时答案也可能会有这么多,因此其时间和空间复杂度都非常高,到了这个量级就不能暴力了,而且在这过程中还要考虑到元素的去重问题,所以仅限于在本地跑着玩。
代码/Python 参考
class Solution:
def threeSum(self, nums: list[int]) -> list[list[int]]:
res = []
n = len(nums)
for i in range(n):
for j in range(n):
for k in range(n):
if i != j and i != k and k != j and nums[i] + nums[j] + nums[k] == 0:
res.append([nums[i], nums[j], nums[k])
# 去重略
return res
方法二:哈希
前面说过这题和两数之和有点像,所以我们还可以把问题转换为对数组中的每个x,将其作为target,求剩余元素中的两数之和问题。如果用两数之和中哈希表的做法,时间复杂度可以降到O(N2),但同时还要考虑到如何进行元素去重的问题,所以仍然可能会超时。
代码/Python参考
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
# 转换为两数之和问题
res = defaultdict(int)
ans = []
pos = dict()
for i, x in enumerate(nums):
target = 0 - x
for j, y in enumerate(nums):
if i != j:
tmp = target - y
if tmp in pos and pos[tmp] != j and pos[tmp] != i:
res[tuple(sorted([x, y, tmp]))] += 1
else:
pos[y] = j
# 用字典的键实现去重
return list(map(list, res.keys()))
方法三:排序+双指针
前面提到的去重问题,我觉得也是本题的考点之一,那我们让每个答案三元组(a,b,c)都是非递减的,同时三元组整体也是非递减的,那不就能保证答案不会重复了吗,所以我们先排序原数组,同时用上一个方法的思想,将每个x都视为一次target,然后使用双指针的方法找到答案。具体的解释见代码里:
代码/Python
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
res = []
nums.sort()
n = len(nums)
pre = 1000001
for i in range(n):
if nums[i] == pre: # 保证不重复
continue
if nums[i] > 0: # 排序后,a>0,后面不可能有b,c使得a+b+c=0
break
pre = nums[i]
a = nums[i]
target = -a # 每个target=-a
p2 = n - 1 # 后指针
p1pre = 1000001
for p1 in range(i + 1, n - 1): #前指针
if p1pre == nums[p1]: # 保证不重复
continue
b = nums[p1]
p1pre = b
while p1 < p2:
c = nums[p2]
if b + c < target: # 保证前指针的数先找完
break
elif b + c > target:
p2 -= 1
else:
res.append([a, b, c])
p2 -= 1
break # 找到一个答案,退出while,让前指针后移
return res
代码/C++
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
int n = nums.size(), pre, p1pre, i, target, a, b, c, p1, p2;
pre = 1000001;
for(i = 0; i < n; i++){
if(nums[i] == pre){
continue;
}
if(nums[i] > 0){
break;
}
a = nums[i];
pre = a;
target = -a;
p2 = n - 1;
for(p1 = i + 1; p1 < n; p1++){
if(p1pre == nums[p1]){
continue;
}
b = nums[p1];
p1pre = b;
while(p1 < p2){
c = nums[p2];
if(b + c < target){
break;
}
else if(b + c > target){
p2--;
}
else{
res.push_back({a, b, c});
p2--;
break;
}
}
}
}
return res;
}
};
总结
前两种方法都是O(N3),第三种优化后是O(N2),空间上都是O(1)。