题目来源:https://leetcode-cn.com/problems/3sum/
大致题意:
给一个数组,求出所有三个元素之和为 0 的数对
思路
题目要求不能有重复的数对,若直接遍历所有可能一定会有重复数对出现
对于 a + b + c = 0,只要保证所有的 a <=b <= c,就可以避免出现重复的数对。于是可以先对数组排序,然后从小到大枚举所有数,同时注意,枚举每个元素时,若其与上轮枚举的该元素值相同,则跳过
但是这样遍历的复杂度仍为 O(n3),复杂度过高
可以注意到,在我们枚举出 a 和 b 后,只会有一个满足题意的 c,使 a + b +c = 0,而在之后的枚举中,a 和 b 的值都会增加(因为排序了,从小到大枚举),所以满足题意的 c 的值应该较少,也就是说随着从小到大枚举前两个元素的同时,第三个元素的值在减少,即可以从大到小的枚举第三个元素
- 具体的,在每次固定一个 a 后,枚举所有可能的 b 的同时,可以通过一个索引 idx 标记可能满足题意的 c 的位置,若当前 a + b + c > 0,那么之后满足题意的 c 的索引 idx 一定比当前要小,即需要更新 idx
这样做保证了在枚举所有的 a 时,对于所有的 b 和 c 只需要一轮遍历即可,即复杂度为 O(n2)
代码:
public List<List<Integer>> threeSum(int[] nums) {
int n = nums.length;
List<List<Integer>> ans = new ArrayList<>();
// 排序
Arrays.sort(nums);
// 枚举所有的 a
for (int i = 0; i < n; i++) {
// 若当前枚举的数与上一个相同,为避免出现重复数对,跳过
if (i != 0 && nums[i] == nums[i - 1]) {
continue;
}
// 标记 c 可能的索引
int thirdIdx = n - 1;
// 枚举所有的 b
// b 对应的索引应该小于 c 对应的索引
for (int j = i + 1; j < thirdIdx; j++) {
// 若当前枚举的数与上一个相同,为避免出现重复数对,跳过
if (j != i + 1 && nums[j] == nums[j - 1]) {
continue;
}
// 若当前的 a + b + c > 0,那么 c 过大,需要往前找
while (j < thirdIdx && nums[i] + nums[j] > -nums[thirdIdx]) {
thirdIdx--;
}
// b == c,不会再有满足题意的数对,跳出循环
if (thirdIdx == j) {
break;
}
// a + b + c = 0
if (nums[i] + nums[j] + nums[thirdIdx] == 0) {
List<Integer> list = new ArrayList<>();
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[thirdIdx]);
ans.add(list);
}
}
}
return ans;
}