Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.
Note:
- Elements in a triplet (a,b,c) must be in non-descending order. (ie, a ≤ b ≤ c)
- The solution set must not contain duplicate triplets.
For example, given array S = {-1 0 1 2 -1 -4}, A solution set is: (-1, 0, 1) (-1, -1, 2)
与上一题2Sum类似,但也有不同。
在2Sum中,对输出没有排序要求,也假设只有一个solution,因此,仅用一次遍历,找到可能值即返回就可以。
在3Sum中,需要排序,并且返回所有可能值同时不能有重复,因此简单的遍历不成功了。但其实3Sum分解出来就是先找一个数,让其相反数作为2Sum的target,然后运行一遍2Sum。在上一篇博客中的2Sum算法仅针对2Sum题目要求,不具备返回所有可能结果以及去重的功能。因此需要一个更好的2Sum算法。最直观的思路就是先选一个数,令其相反数作为target,然后在剩余的数当中选两个,让他们相加等于这个数。但如果输入是无序的,这个过程需要耗费O(n3)的时间,很显然太naive了。
一个相对好的方法是对输入数组排序,然后从最小数开始,选出一个的相反数作为target,然后在剩下的数中用两个指针,分别指向剩余数组的首尾。首指针start递增,尾指针end递减。如果首尾相加刚好等于target,说明找到一个符合条件的解。如果首尾相加小于target,意味着两个加数太小,需要递增start指针。如果首尾相加大于target,意味着两个加数太大,需要递减end指针。当然在指针递增和递减的过程中,要考虑略过已经测试过的重复值。这样一来,算法复杂度减小为O(n2)。因此最初的排序算法用O(nlogn)也不会对复杂度产生影响。具体代码如下:
public List<List<Integer>> threeSum(int[] num) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
if (num == null || num.length < 3) {
return res;
}
Arrays.sort(num);
for (int i = 0; i < num.length - 2; i++) {
if (i == 0 || num[i] > num[i - 1]) {
int negate = -num[i];
int start = i + 1;
int end = num.length - 1;
while (start < end) {
if (num[start] + num[end] == negate) {
List<Integer> item = new ArrayList<Integer>();
item.add(num[i]);
item.add(num[start]);
item.add(num[end]);
res.add(item);
start++;
end--;
while (start < end && num[start] == num[start - 1]) {
start++;
}
while (start < end && num[end] == num[end + 1]) {
end--;
}
} else if (num[start] + num[end] < negate) {
start++;
} else {
end--;
}
}
}
}
return res;
}
3Sum closest
Given an array S of n integers, find three integers in S such that the sum is closest to a given number, target. Return the sum of the three integers. You may assume that each input would have exactly one solution.
For example, given array S = {-1 2 1 -4}, and target = 1. The sum that is closest to the target is 2. (-1 + 2 + 1 = 2).
这是一个和3Sum相似的问题,也可以用相同的思路解决。不同之处在于需要维护一个最小偏差。
public int threeSumClosest(int[] num, int target) {
if (num == null || num.length < 3) {
return 0;
}
Arrays.sort(num);
int minDif = Integer.MAX_VALUE;
int closest = 0;
for (int i = 0; i < num.length - 2; i++) {
int start = i + 1;
int end = num.length - 1;
while (start < end) {
int tmp = num[i] + num[start] + num[end];
int dif = tmp - target;
if (Math.abs(dif) < minDif) {
minDif = Math.abs(dif);
closest = tmp;
}
if (tmp < target) {
start++;
} else {
end--;
}
}
}
return closest;
}