一、题目
给你一个长度为 n
的整数数组 nums
和 一个目标值 target
。请你从 nums
中选出三个整数,使它们的和与 target
最接近。
返回这三个数的和。
假定每组输入只存在恰好一个解。
二、解题思路
解决这个问题的关键在于找到一个有效的算法来遍历数组并找到三个数的组合,使得它们的和最接近目标值。这里有一个常见的解题思路,即使用双指针技巧。
以下是解决这个问题的 Java 实现步骤:
- 首先,对数组进行排序,这样我们可以确保在遍历数组时,我们能够以线性时间复杂度找到最接近目标值的三个数。
- 初始化一个变量来存储当前找到的最接近目标值的和,以及一个变量来存储当前的最小差值。
- 使用一个循环遍历数组(除了第一个元素),对于数组中的每个元素(称为固定元素),使用两个指针分别指向固定元素的下一个元素和数组的最后一个元素。
- 在每次迭代中,计算三个元素的和,并与目标值的差值进行比较。如果这个差值小于当前的最小差值,更新最小差值和最接近的和。
- 移动指针以尝试找到更接近目标值的组合。如果当前和大于目标值,移动较小的指针(因为它会使和减小),如果当前和小于目标值,移动较大的指针(因为它会使和增大)。
- 当遍历完成后,返回最接近目标值的和。
三、具体代码
import java.util.Arrays;class Solution {public int threeSumClosest(int[] nums, int target) {// Step 1: Sort the arrayArrays.sort(nums);// Step 2: Initialize variablesint closestSum = nums[0] + nums[1] + nums[nums.length - 1];int minDiff = Math.abs(target - closestSum);// Step 3: Loop through the arrayfor (int i = 0; i < nums.length - 2; i++) {// Step 4: Use two pointersint left = i + 1;int right = nums.length - 1;while (left < right) {// Step 5: Calculate the sum and find the closestint currentSum = nums[i] + nums[left] + nums[right];int currentDiff = Math.abs(target - currentSum);// Update closestSum and minDiff if necessaryif (currentDiff < minDiff) {closestSum = currentSum;minDiff = currentDiff;}// Step 6: Move pointersif (currentSum < target) {left++; // Increase sum} else if (currentSum > target) {right--; // Decrease sum} else {// We found the exact sum, break the loopbreak;}}}return closestSum;}
}
四、时间复杂度和空间复杂度
1. 时间复杂度
- 时间复杂度是 O(n^2)。
- 数组排序操作
Arrays.sort(nums)
的时间复杂度是 O(n log n),其中 n 是数组的长度。 - 接下来是两层循环。外层循环遍历数组,除了第一个元素,所以最多执行 n-1 次。内层循环使用两个指针,最坏情况下会遍历整个数组,即 O(n)。因此,内层循环的时间复杂度是 O(n)。
- 综合考虑,整个算法的时间复杂度是 O(n log n)(排序)加上 O(n^2)(双层循环),但由于 O(n^2) 通常比 O(n log n) 大得多,所以总的时间复杂度是 O(n^2)。
2. 空间复杂度
- 空间复杂度是 O(1)。
- 除了输入数组和一些辅助变量(如
closestSum
和minDiff
)之外,没有使用额外的数据结构。这些辅助变量的空间需求是常数级别的。 - 因此,空间复杂度是 O(1),即常数空间复杂度。
五、总结知识点
-
数组排序:使用
Arrays.sort(nums)
方法对输入的整数数组进行排序。这是解决这个问题的基础,因为排序后的数组可以使得后续的双指针算法更加高效。 -
双指针技巧:在排序后的数组中,通过使用两个指针(left 和 right)来遍历数组,寻找和目标值最接近的三个数。这种技巧常用于解决涉及数组和目标值比较的问题。
-
循环控制:代码中使用了
for
循环和while
循环来控制算法的执行流程。for
循环用于遍历数组的每个元素,而while
循环则用于在找到合适的三个数之前不断调整指针位置。 -
条件判断:在
while
循环中,通过比较当前和currentSum
与目标值target
的大小关系,决定是移动左指针(增加和)还是右指针(减少和)。 -
数学运算:使用
Math.abs(target - currentSum)
来计算当前和与目标值的绝对差值,这是确定最接近目标值的关键步骤。 -
变量更新:在找到更接近目标值的和时,更新
closestSum
和minDiff
变量。这是动态编程思想的一个体现,即在每一步中都保留当前最优解。 -
算法效率:代码中考虑了算法的效率,通过排序和双指针技巧,将原本可能需要 O(n^3) 时间复杂度的问题降低到了 O(n^2)。
-
边界条件处理:在
while
循环中,当找到和等于目标值时,使用break
语句提前结束循环,这是一种边界条件处理,确保算法在找到最优解后不会继续执行不必要的操作。 -
代码可读性:代码结构清晰,注释详细,有助于理解算法的每一步。良好的代码可读性对于维护和理解代码非常重要。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。