题目
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum
思路:
这个题有两个点:(1)需要找到三个数字,和为0(2)找到的所有组合不重复
第一个点是基本功能点,第二个点可以先不考虑,想清楚第一个点之后再考虑第二个点。其实第二个点就是通过避免本次遍历查找的数字和上次查找的数字相同来避免重复的。
官方的解题思路写的非常好,在一开始就考虑了第二个点,但是我还是写一下自己直观的思路。另外官方的双指针法不太简洁。
暴力法
用暴力法解这个题的思路很直接,三层循环分别找三个数字,看这三个数字的和是否为0。时间复杂度为O(N^3)。
但是如何避免重复呢?比较难。很容易想到,在有序数组中查找的时候是很方便避免重复的。那么我们可以先把数组排序,每一层循环的时候,下次的数字都和上一次的数字要求不一样,这样就能避免重复了。因为排序的时间复杂度是,整体的时间复杂度就是
。
双指针法
上面进行了排序,我们可以想到,排序后其实第二层循环和第三层循环可以合并为一层,因为数据是有序的,我们可以知道该往哪个方向进行遍历。这时的时间复杂度可以降低为
代码如下:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
Integer first = null;
for (int i = 0; i < nums.length; i++) {
// 第一个数字,和上次需要保持不一样
Integer current1 = nums[i];
if (current1.equals(first)) {
continue;
} else {
first = current1;
}
// 双指针法
Integer second = null, third = null;
int left = i + 1, right = nums.length - 1;
while (right > left) {
second = nums[left];
third = nums[right];
int currentSum = current1 + second + third;
if (currentSum == 0) {
result.add(generateList(first, second, third));
// 第二、三个数字要和上次不一样
while (right > left && second.equals(nums[left])) {
left++;
}
while (right > left && third.equals(nums[right])) {
right--;
}
} else if (currentSum < 0) {
// 第二个数字要和上次不一样
while (right > left && second.equals(nums[left])) {
left++;
}
} else {
// 第三个数字要和上次不一样
while (right > left && third.equals(nums[right])) {
right--;
}
}
}
}
return result;
}
public List<Integer> generateList(Integer first, Integer second, Integer third) {
List<Integer> result = new ArrayList<>();
result.add(first);
result.add(second);
result.add(third);
return result;
}
public static void main(String[] args) {
int nums[] = {-1,0,1,2,-1,-4};
// int nums[] = {1,-1,-1,0};
Solution solution = new Solution();
solution.threeSum(nums);
}
}
这个代码有些地方其实是可以进行优化的。比如,和上一次不一致,不一定非要保留上一次的变量,只需要判断数组相邻位置即可。伪代码如下(参考官方解答思路)
nums.sort()
for first = 0 .. n-1
// 只有和上一次枚举的元素不相同,我们才会进行枚举
if first == 0 or nums[first] != nums[first-1] then
for second = first+1 .. n-1
if second == first+1 or nums[second] != nums[second-1] then
修改后代码如下:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
// 第一个数字,和上次需要保持不一样
if (i > 0 && nums[i] == nums[i - 1]) continue;
int first = nums[i];
// 双指针法
int left = i + 1, right = nums.length - 1;
while (right > left) {
if (left > i + 1 && nums[left] == nums[left - 1]) {
left++;
continue;
}
if (right < nums.length - 1 && nums[right] == nums[right + 1]) {
right--;
continue;
}
int second = nums[left];
int third = nums[right];
int currentSum = first + second + third;
if (currentSum == 0) {
result.add(generateList(first, second, third));
left++;
right--;
} else if (currentSum < 0) {
left++;
} else {
right--;
}
}
}
return result;
}
public List<Integer> generateList(Integer first, Integer second, Integer third) {
List<Integer> result = new ArrayList<>();
result.add(first);
result.add(second);
result.add(third);
return result;
}
public static void main(String[] args) {
int nums[] = {-1, 0, 1, 2, -1, -4};
// int nums[] = {1,-1,-1,0};
Solution solution = new Solution();
solution.threeSum(nums);
}
}
总结
这个题其实很简单,但因为多出一个条件,就使复杂度高了一些。做题时可以先抓重点矛盾去解决,然后去考虑怎么实现另一个条件。但是熟练的情况下,最好是一起考虑,这样能互相辅助去解决问题。
耗时:75分钟。