题目
输入一个数组,如何找出数组中所有和为0的3个数字的三元组?需要注意的是,返回值中不得包含重复的三元组。例如,在数组[-1,0,1,2,-1,-4]中有两个三元组的和为0,它们分别是[-1,0,1]和[-1,-1,2]。
这道题是“排序数组中的两个数字之和”的加强版。不了解的可以先去了解一下。
排序数组中的两个数字之和
所以这道题我们可以先排序,然后固定一个数i,然后该题就转换为在排序数组中查找和为-i的两个数。排序算法时间复杂度一般为O(nlogn),排序数组中找出和为给定值的方法时间复杂度为O(n),由于要固定数组中每个数字,故查找三元组的时间复杂度为O(n^2),所以总体时间复杂度为O(nlogn)+O(n^2),仍是O(n^2)。
题目还要求去除重复三元组,所以在找到一个三元组后,需要跳过相同的值。详细代码如下:
public static List<List<Integer>> threeSum(int[] nums) {
//LinkedList底层是一个双向链表结构:增删快,查询慢
List<List<Integer>> result = new LinkedList<>();
//数组长度最起码要大于三
if (nums.length >= 3) {
//Arrays类里主要是一些操作数组的方法
Arrays.sort(nums);
int i = 0;
//待扫描元素个数应大于三
while (i < nums.length - 2) {
//固定一个数nums[i],题目即可看作在排序数组中查找和为-nums[i]的两个数字
twoSum(nums, i, result);
//因为排过序了,相同元素肯定挨着,判断过一次就没必要再重复判断
//所以记录下该次判断过的元素,然后跳过重复元素
int temp = nums[i];
//跳过重复元素,同时保证不越界
while (nums[i] == temp && i < nums.length) {
//数字nums[i]重复就跳过
i++;
}
}
}
return result;
}
private static void twoSum(int[] nums, int i, List<List<Integer>> result) {
//双指针思想
int j = i + 1;
int k = nums.length - 1;
//左指针大于等于右指针时跳出循环
while (j < k) {
if (nums[i] + nums[j] + nums[k] == 0) {
//满足条件就调用方法把数组转变为集合加入到结果中
result.add(Arrays.asList(nums[i], nums[j], nums[k]));
//记录该次满足条件的组合
int temp = nums[j];
//去除重复三元组,同时保证指针不越界
while (nums[j] == temp && j < k) {
j++;
}
} else if (nums[i] + nums[j] + nums[k] < 0) {
//和太小,将左指针右移
j++;
} else {
//和太大,将右指针左移
k--;
}
}
}
另一种写法
public static List<List<Integer>> threeSum(int[] nums) {
// 对数组进行排序
Arrays.sort(nums);
int n = nums.length;
List<List<Integer>> result = new ArrayList<>();
// 遍历数组
for (int i = 0; i < n; i++) {
// 跳过重复的元素
if (i > 0 && nums[i] == nums[i - 1]) continue;
// 使用两个指针,一个从当前元素的下一个位置开始,一个从数组末尾开始
int j = i + 1, k = n - 1;
while (j < k) {
// 跳过重复的元素,确保不会重复计算相同的三元组
while (j > i + 1 && j < n && nums[j] == nums[j - 1]) j++;
// 如果两指针相遇或越过,跳出循环
if (j >= k) break;
// 计算当前三元组的和
int sum = nums[i] + nums[j] + nums[k];
// 如果和为零,将三元组加入结果列表,同时将左指针向右移动
if (sum == 0) {
result.add(Arrays.asList(nums[i], nums[j], nums[k]));
j++;
} else if (sum > 0) {
// 如果和大于零,说明右边的元素太大,将右指针向左移动
k--;
} else if (sum < 0) {
// 如果和小于零,说明左边的元素太小,将左指针向右移动
j++;
}
}
}
return result;
}