题目
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
分析
这种题一看就知道需要数组遍历了,需要思考的就是如何更高效地遍历,如何不重复自己已经走过的路,这里采用排序+双指针的方式来实现对这道题的解;接下来就是如何去重了,答案要求不可以存在重复的三元组。首先讲一下思路:
-
首先对数组进行排序,将数组变为升序数组;
-
开始遍历,i指针指向nums[i],新建一个左指针指向nums[i+1],另外一个右指针指向数组最后一个元素,然后计算三个值的和sum是否为0;
-
当sum < 0 时,此时i指针还没进入下一循环,是固定的,所以只能将左指针往右移,以期望三值之和能趋进于0或等于0;
-
当sum > 0时,需要将右指针左移,以期望三值之和能趋近于0或等于0;
-
如果sum == 0,那么此时就可以将三个值新建各数组存起来了,接下来不能只移动一个指针了,需要右指针左移,左指针右移;但是在移动前还需要继续判断,因为题目要求“答案中不可以包含重复的三元组”。
-
如果左指针的下一位与现在相等,即
nums[left] == nums[left+1]
,那么应该跳过这一节点,因为这样会使得结果重复,所以一直left++
再判断,直到新的left点的元素不相等为止。 -
如果右指针的前一位与现在相等,即
nums[right] == nums[right-1]
,那么应该跳过这一节点,因为这样会使得结果重复,所以一直right--
再判断,直到新的right点的元素不相等为止。 -
继续循环判断,直到不满足
left < right
为止,进入下一循环; -
进入新的循环,需要判断
nums[i] == nums[i-1]
,因为新的i指针如果与旧的相同,那么同样会产生重复的结果,需要跳过; -
该算法的时间复杂度为O(n^2),n 为数组长度。
接下来说几个无聊优化(鸡肋优化):
-
如果数组长度小于3,那么压根没有3数之和的可能,返回空数组结果即可;
-
在每次进入循环的时候,可以判断一下i指针指向的节点的值是否大于0,如果大于0,那么3数之和一定大于0,直接跳过;
-
数组的遍历并不需要将数组最后两位也遍历,所以不需要
i < nums.length
,改为i < nums.length - 2
;
这优化真够鸡肋,没啥用。
代码实现
public class LeetCode15 {
public static List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new LinkedList<List<Integer>>();
if (nums.length < 3) return result;
Arrays.sort(nums);
for (int i = 0; i < nums.length - 2; i++) {
if(nums[i] > 0) break; // 如果当前i指针指向数大于0,那么3数之和一定大于0,直接跳出
int left = i + 1, right = nums.length - 1;
if (i > 0 && nums[i] == nums[i-1]) continue;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
while (left < right && nums[left] == nums[left + 1]) left++;
while (left < right && nums[right] == nums[right - 1]) right--;
left++;
right--;
} else if (sum < 0)
left++;
else
right--;
}
}
return result;
}
public static void main(String[] args) {
int[] nums = new int[]{-1, 0, 1, 2, -1, -4};
System.out.println(threeSum(nums));
}
}