明确题意:
1,给定一个整数数组nums,在其中找3个整数,它们的下标不相同。
2,3个整数的和等于0。
3,返回满足1,2的不重复的所有三元组。
思路分析:
方式一:暴力解法
三层for循环确定每一个数,筛选出和为0的。然后需要将得到的结果去重(使用哈希表)。
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums); // 先将数组排序
List<List<Integer>> result = new ArrayList<>();
int n = nums.length;
Map<Integer, Integer> map = new HashMap<>();
// 将数组中的所有元素存入哈希表中
for (int i = 0; i < n; i++) {
map.put(nums[i], i);
}
// 枚举所有可能的三个数的组合
for (int i = 0; i < n - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue; // 跳过重复的元素
for (int j = i + 1; j < n - 1; j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) continue; // 跳过重复的元素
int target = 0 - nums[i] - nums[j];
if (map.containsKey(target) && map.get(target) > j) {
List<Integer> list = new ArrayList<>();
list.add(nums[i]);
list.add(nums[j]);
list.add(target);
result.add(list);
}
}
}
return result;
}
方式二:双指针+排序(去重)
「不重复」的本质是什么?我们保持三重循环的大框架不变,只需要保证:
- 第二重循环枚举到的元素不小于当前第一重循环枚举到的元素;
- 第三重循环枚举到的元素不小于当前第二重循环枚举到的元素。
也就是说,我们枚举的三元组 (a, b, c)(a,b,c) 满足a≤b≤c,保证了只有 (a, b, c)(a,b,c) 这个顺序会被枚举到,而 (b, a, c)(b,a,c)、(c, b, a)(c,b,a) 等等这些不会,这样就减少了重复。要实现这一点,我们可以将数组中的元素从小到大进行排序,随后使用普通的三重循环就可以满足上面的要求。
同时,对于每一重循环而言,相邻两次枚举的元素不能相同,否则也会造成重复。举个例子,如果排完序的数组为[0, 1, 2, 2, 2, 3] 我们使用三重循环枚举到的第一个三元组为 (0, 1, 2)(0,1,2),如果第三重循环继续枚举下一个元素,那么仍然是三元组 (0, 1, 2)(0,1,2),产生了重复。因此我们需要将第三重循环「跳到」下一个不相同的元素,即数组中的最后一个元素 33,枚举三元组 (0, 1, 3)(0,1,3)。
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums); // 先将数组排序
List<List<Integer>> result = new ArrayList<>();
int n = nums.length;
// 枚举所有可能的三个数的组合
for (int i = 0; i < n - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue; // 跳过重复的元素
int left = i + 1, right = n - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
left++;
right--;
while (left < right && nums[left] == nums[left - 1]) left++; // 跳过重复的元素
while (left < right && nums[right] == nums[right + 1]) right--; // 跳过重复的元素
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}
return result;
}