给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
解一:
首先我们会想到暴力求解,也就是用三重for循环依次遍历,在循环前我们需要先对输入数组进行排序,防止重复项(类似[-1,2,-1],[2,-1,-1]这种被认为是重复项)找出所有可能的三元组:
public List<List<Integer>> threeSum1(int[] nums) {
Set<List<Integer>> resultSet = new LinkedHashSet<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
for (int k = j + 1; k < nums.length; k++) {
if (nums[i] + nums[j] + nums[k] == 0) {
List<Integer> list = Arrays.asList(nums[i], nums[j], nums[k]);
resultSet.add(list);
}
}
}
}
return new ArrayList<>(resultSet);
}
执行下,发现超时了:
leetcode12.png
此实现三从for循环,时间复杂度是O(n^3)
解二:
类似于两数之和的升级版,a + b + c = 0,可以理解为-c = a + b,也就是说我们可以进行双重循环i和j,获得-(i+j)的和得到target,判断target是否在缓存中(map吧),因为在map查找key是O(1)时间复杂度,所以可以将解法一的平均时间复杂度降到O(n^2):
public List<List<Integer>> threeSum2(int[] nums) {
if (nums == null || nums.length < 2) {
return null;
}
Arrays.sort(nums);
Set<List<Integer>> resultList = new LinkedHashSet<>();
for (int i = 0; i < nums.length - 1; i++) {
Map<Integer, Integer> cache = new HashMap<>(nums.length);
for (int j = i + 1; j < nums.length; j++) {
int target = -nums[i] - nums[j];
Integer v = cache.get(target);
if (v != null) {
List<Integer> list = Arrays.asList(nums[i], v, nums[j]);
resultList.add(list);
} else {
cache.put(nums[j], nums[j]);
}
}
}
return new ArrayList<>(resultList);
}
执行下:
leetcode13.png
终于执行过了,不过这个执行结果,耗时和内存消耗,差了太多了吧~
解法三:
比较巧妙的一种解法是,先将数组排好序(排序的话时间复杂度为O(Nlogn)),然后指定一个节点(pivot),看其右侧数组中有没有和此节点相加为0的,右侧数组具体查找规则为:生成两个指针分别指向该右侧数组的开始和结尾left,right,将其相加看是否为指定节点的负数(三个数相加为0),如果是的话就将其加入结果集中,如果不是,则看下三个数相加的和大于0或者小于0:
- 如果和大于0的话说明三个数相加超过0了,pivot节点不会动,所以让right节点向左移动一位,继续比较;
- 如果小的话则说明三个数相加的和小于0了,那么只需要left向后移动一位,然后继续比较;
public List<List<Integer>> threeSum3(int[] nums) {
if (nums == null || nums.length < 2) {
return null;
}
//先排序,因为后边的算法依赖有序数组
Arrays.sort(nums);
List<List<Integer>> result = new LinkedList<>();
for (int pivot = 0; pivot < nums.length - 2; pivot++) {
if (nums[pivot] > 0) {
return result;
}
//过滤下,如果pivot相同的话,前面已经处理过了,不需要再次处理
if (pivot != 0 && nums[pivot] == nums[pivot - 1]) {
continue;
}
int left = pivot + 1;
int right = nums.length - 1;
while (left < right) {
int sum = -(nums[left] + nums[right]);
if (nums[pivot] == sum) {
List<Integer> list = Arrays.asList(nums[pivot], nums[left], nums[right]);
result.add(list);
//这边处理下重复性问题,因为当left++ == left时,相同的数
//据已经被处理过了,所以直接过滤掉即可
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
}
//如果结果集>0 右指针--
if (sum < nums[pivot]) {
right--;
} else {
left++;
}
}
}
return result;
}
再次执行下:
leetcode15.png
很明显的性能提升有没有~~~