題目
給定一個包括 n 個整數的數組 nums
和 一個目標值 target
。找出 nums
中的三個整數,使得它們的和與 target
最接近。返回這三個數的和。假定每組輸入只存在唯一答案。
示例:
輸入:nums = [-1,2,1,-4], target = 1
輸出:2
解釋:與 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
提示:
3 <= nums.length <= 10^3
-10^3 <= nums[i] <= 10^3
-10^4 <= target <= 10^4
解題思路
我們可以考慮先對整個數組進行升序排序,首先考慮枚舉第一個元素 a,對於剩下的兩個元素 b 和 c,希望它們的和最接近 target−a。假設數組的長度爲 n,我們先枚舉 a,它在數組中的位置爲 i。爲了防止重複枚舉,我們在位置 [i+1,n) 的範圍內枚舉 b 和 c。我們用 p_b 和 p_c 分別表示指向 b 和 c 的指針,初始時 p_b 指向位置 i+1,即左邊界;p_c 指向位置 n-1,即右邊界。在每一步枚舉的過程中,我們用 a+b+c 來更新答案:
1)如果 a+b+c = target,直接返回 target 作爲答案,因爲不會有再比這個更接近 target 的值了;
2)如果 a+b+c > target,那麼就將 p_c 向左移動一個位置;
3)如果 a+b+c < target,那麼就將 p_b 向右移動一個位置。
本題也有一些可以減少運行時間(但不會減少時間複雜度)的小優化,當我們枚舉 a, b, c 中任意元素時,如果出現和上一個相同的,可以直接將其移動到下一個與這次枚舉到的不相同的元素,減少枚舉的次數。
複雜度分析:
時間複雜度:O(N^2)。我們首先需要 O(NlogN) 的時間對數組進行排序,隨後在枚舉的過程中,使用一重循環 O(N) 枚舉 a,雙指針 O(N) 枚舉 b 和 c,故一共是 O(N^2)。
空間複雜度:O(logN)。排序需要使用 O(logN) 的空間。然而我們修改了輸入的數組 nums,在實際情況下不一定允許,因此也可以看成使用了一個額外的數組存儲了 nums 的副本並進行排序,此時空間複雜度爲 O(N)。
代碼
原始版
class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int n = nums.length;
int best = nums[0] + nums[1] + nums[2];
for(int a=0; a<n-2; a++){
int b = a+1;
int c = n-1;
while(b<c){
int cursum = nums[a] + nums[b] + nums[c];
if(cursum == target){
// 肯定是最接近的,直接返回。
return target;
}
if(Math.abs(target-cursum) < Math.abs(target-best)){
best = cursum;
}
if(cursum > target){
c--;
}else{
b++;
}
}
}
return best;
}
}
優化版
class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int n = nums.length;
int best = nums[0] + nums[1] + nums[2];
for(int a=0; a<n-2; a++){
if(a>0 && nums[a]==nums[a-1]){
continue;
}
int b = a+1;
int c = n-1;
while(b<c){
int cursum = nums[a] + nums[b] + nums[c];
if(cursum == target){
// 肯定是最接近的,直接返回。
return target;
}
if(Math.abs(target-cursum) < Math.abs(target-best)){
best = cursum;
}
if(cursum > target){
int c0 = c-1;
while(c0>b && nums[c0]==nums[c]){
c0--;
}
c = c0;
}else{
int b0 = b+1;
while(b0<c && nums[b0]==nums[b]){
b0++;
}
b = b0;
}
}
}
return best;
}
}