给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
例如,给定数组 nums = [-1,2,1,-4], 和 target = 1. 与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).
解题思路:
参考: leetcode 15 三数之和 3Sum JAVA实现
由于已经做过了15题 三数之和。本题的思路可以仿照15的题的思路,将三数之和问题间接转变为二数之和,利用双指针再次减少比较次数。
先将数组进行排序,如给定的数组 nums = [-1,2,1,-4], 和 target = 1.
排序后的结果为 nums = [-4,-1,1,2]
令i从0循环到nums.length-2,l=i+1,r=nums.length-1.
a = nums[i] ; b = nums[l]; c = nums[r];
需要用变量sum记录下a+b+c之和,sum = a+b+c;
需要用变量min记录下a+b+c与target的最小距离,min = Math.abs(sum -target);并且在每一次更新sum后进行判断是否更新min的值。
如果sum>target的值,那么r应该左移
如果sum<target的值,那么l应该右移
考虑一下特殊情况,当nums数组不足三个元素时,默认返回target;当nums数组仅有三个元素时,则直接返回nums[0]+nums[1]+nums[2]
我在这里写了一个与15题非常镜像的程序
class Solution {
public int threeSumClosest(int[] nums, int target) {
if(nums.length < 3)//数组不足三个元素
return target;
if(nums.length == 3){//数组仅有三个元素
return nums[0]+nums[1]+nums[2];
}
Arrays.sort(nums);//将数组排序
int min = Integer.MAX_VALUE;//记录 a+b+c - target的最短距离
int res = 0;//记录结果sum值
for(int i=0;i<nums.length-2;i++){
int a = nums[i];
int l = i+1;
int r = nums.length-1;
int sum = 0;
int dif = 0;
while(l<r){
sum = a+nums[l]+nums[r];
dif = Math.abs(sum-target);
if(dif == 0)//如果sum==target
return target;
if(dif<=min){//如果需要更新min的值
min = dif;
res = sum;
if(sum < target){//sum<target l左移
while(l<r && nums[l]==nums[l+1]) l++; //跳过重复
l++;
}else if(sum > target){//sum>target r右移
while(l<r && nums[r] == nums[r-1]) r--; //跳过重复
r--;
}
}
else{//不需要更新min值
if(sum < target){
while(l<r && nums[l]==nums[l+1]) l++;
l++;
}else if(sum > target){
while(l<r && nums[r] == nums[r-1]) r--;
r--;
}
}
}
}
return res;
}
}
代码耗时25ms
这个代码实在太臃肿了,考虑进行优化。
- 1 这个题和15题的区别是 该题是唯一解,因此不必判重,判重的代价太大。
- 2 l和r的移动应该和是否更新min值分开
- 3 考虑这种情况,如果nums[i]+nums[nums.length-2]+nums[nums.length-1]<target,那么此时该范围已经不可能存在大于nums[i]+nums[nums.length-2]+nums[nums.length-1]的数了,则i应该继续移动,才能保证可能找到更接近target的数。
- 4 再考虑这种情况,nums[i]+nums[i+1]+nums[i+2] > target,此时无论i,l,r怎样继续移动,只可能存在比nums[i]+nums[i+1]+nums[i+2]大的数,所以此时直接返回当前的res值,结束程序。
优化后的下述代码耗时12ms
class Solution {
public int threeSumClosest(int[] nums, int target) {
if(nums.length < 3)//数组不足三个元素
return target;
else if(nums.length == 3){//数组仅有三个元素
return nums[0]+nums[1]+nums[2];
}
else{
Arrays.sort(nums);//将数组排序
int min = Integer.MAX_VALUE;//记录 a+b+c - target的最短距离
int res = 0;//记录结果sum值
for(int i=0;i<nums.length-2;i++){
int l = i+1;
int r = nums.length-1;
int sum = 0;
int dif = 0;
if(nums[i]+nums[nums.length-2]+nums[nums.length-1]<target){
sum = nums[i]+nums[nums.length-2]+nums[nums.length-1];
dif = Math.abs(sum-target);
if(dif<min){
min = dif;
res = sum;
}
continue;
}
if(nums[i]+nums[i+1]+nums[i+2] > target){
sum = nums[i]+nums[i+1]+nums[i+2];
dif = Math.abs(sum-target);
if(dif<min){
min = dif;
res = sum;
}
return res;
}
while(l<r){
sum = nums[i]+nums[l]+nums[r];
dif = Math.abs(sum-target);
if(dif == 0)//如果sum==target
return target;
if(dif<min){//如果需要更新min的值
min = dif;
res = sum;
}
if(sum < target)//sum<target l左移
l++;
else if(sum > target)//sum>target r右移
r--;
}
}
return res;
}
}
}
最后贴上排名第一的答案,耗时8ms
class Solution {
public int threeSumClosest(int[] nums, int target) {
if(nums.length<3){
return target;
}
else if (nums.length==3){
return nums[0] + nums[1] + nums[2];
}
else{
int result= nums[0] + nums[1] + nums[2];
int cSum;
Arrays.sort(nums);
for(int i=0; i<nums.length-2; i++){
int j=i+1;
int k=nums.length-1;
if((nums[i] + nums[nums.length-2] + nums[nums.length-1])<target){
cSum = nums[i] + nums[nums.length-2] + nums[nums.length-1];
result = check(target, result, cSum);
continue;
}
if((nums[i] + nums[i+1] + nums[i+2])>target){
cSum = nums[i] + nums[i+1] + nums[i+2];
result = check(target, result, cSum);
continue;
}
while(j<k){
cSum = nums[i] + nums[j] + nums[k];
if(cSum == target){
return cSum;
}
result = check(target, result, cSum);
if(cSum < target){
j++;
}
else if(cSum > target){
k--;
}
}
}
return result;
}
}
private int check(int target, int result, int cSum){
if(Math.abs(target - cSum)<Math.abs(target - result)){
result = cSum;
}
return result;
}
}