携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第19天,点击查看活动详情
三数之和
给你一个包含 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 = []
输出: []
示例 3:
输入: nums = [0]
输出: []
提示:
0 <= nums.length <= 3000-105 <= nums[i] <= 105
解题思路:哈希表
这道题可以使用哈希解法
题目要求 a + b + c = 0,c = -(a + b).我们可以每次枚举a和b,在数组里寻找c是否存在
通过两层for循环来枚举a和b的值,用哈希通过O(1)的时间在数组里取寻找-(a+b)
虽然思路是正确的,但是我们还会遇到一个问题,题目要求答案不可以包含重复的三元组。我们不光要找出正确的三元组,还需要把结果集进行去重,难点就在这去重上,我直接上代码,代码里有注释,帮助大家更好的理解
代码:(JAVA实现)
public static List<List<Integer>> threeSum(int[] nums) {
//如果数组里的元素个数 < 3,就不需要往下进行了,直接返回
if (nums.length < 3) {
return new ArrayList<List<Integer>>();
}
//排序,方便我们去重
Arrays.sort(nums);
List<List<Integer>> list = new ArrayList<>();
for (int i = 0; i < nums.length;i++) {
//排序之后nums的第一个数如果 > 0,也不需要往下进行了,根本无法凑成三元组
if (nums[i] > 0) {
break;
}
//元素的去重
if (i > 0 && nums[i] == nums[i-1]) {
continue;
}
//用set来存储-(a+b)这个数
HashSet<Object> set = new HashSet<>();
for (int j = i+1; j < nums.length;j++) {
//这步也是为了去除重复元素
if (j > i+2
&& nums[j] == nums[j-1]
&& nums[j] == nums[j-2]) {
continue;
}
//如果nums[j]不在数组里,就把[-a-b]添加到set里
if (!set.contains(nums[j])) {
set.add(-nums[i]-nums[j]);
}else {
List<Integer> res = new ArrayList<>();
res.add(nums[i]);
res.add(nums[j]);
res.add(-nums[i]-nums[j]);
list.add(res);
set.remove(-nums[i]-nums[j]);
//还是去重操作
while (j < nums.length-1 && nums[j] == nums[j+1]) {
j++;
}
}
}
}
//返回
return list;
}
复杂度分析
- 时间复杂度: O(n^2)
- 空间复杂度: O(1)
提交结果
解题思路:排序+双指针
其实本题更适合用双指针来做,因为哈希的去重操作太麻烦了,如果刚做这道题的人,想到了用哈希的思路,也不一定能把去重操作写出来,所以我还是比较喜欢用双指针来做这道题,更直观,也更简单些
思路其实很简单:
- 首先将数组先排序,也是方便我们进行去重操作
- 定义一个
for循环,i从数组下标0的地方开始,同时再定义左右指针,左指针left在i+1的位置,右指针right在nums.length-1的位置 - 每次计算这三个数
nums[i] + nums[left] + nums[right]的和sum,是否满足0,满足添加到结果集里 如果nums[i] == nums[i-1],结果会重复,应该跳过
sum > 0,说明这个数大了,需要移动right,因为已经排序过的关系,越靠左的数越小,right就应该向左移动一格sum < 0,说明这个数小了,left向右移动一格sum == 0,这个数刚好满足0,将结果添加到结果集里当sum == 0时,如果nums[left] == nums[left+1]则会导致结果重复,应该跳过,left++当sum == 0时,如果nums[right] == nums[right-1]则会导致结果重复,应该跳过,right--
代码:(JAVA实现)
public static List<List<Integer>> threeSum(int[] nums) {
//如果数组里的元素个数 < 3,就不需要往下进行了,直接返回
if (nums.length < 3) {
return new ArrayList<List<Integer>>();
}
List<List<Integer>> list = new ArrayList<>();
//排序
Arrays.sort(nums);
//确定一个数组,将三数之和转换成两数之和
for (int i = 0; i < nums.length; i++) {
//当nums的第一个元素代表后面的元素无法构成要求的三元组,直接跳过即可
if (nums[i] > 0) {
break;
}
//先确定第一位数字,去寻找剩下的两个数字的组合,当第一位数字重复时代表在之前就已经枚举过了,需要去除
if (i > 0 && nums[i] == nums[i-1]) {
continue;
}
//设置左右指针,左右指针一定要比第一个数大
int left = i+1;
int right = nums.length-1;
while (right > left) {
int sum = nums[i]+nums[left]+nums[right];
if (sum < 0) {
left++;
}else if(sum > 0) {
right--;
}else {
List<Integer> res = new ArrayList<>();
res.add(nums[i]);
res.add(nums[left]);
res.add(nums[right]);
list.add(res);
while (right > left && nums[left] == nums[left+1]) {
left++;
}
while (right > left && nums[right] == nums[right-1]) {
right--;
}
//缩小指针范围
left++;
right--;
}
}
}
return list;
}
复杂度分析
- 时间复杂度:O(n^2)
- 空间复杂度:O(1)