三数之和问题要求我们在给定的整数数组中找到所有不重复的三元组,使得这三个数的和恰好为零。这个问题看似简单,但有几个关键点需要注意:
提示:在实际面试中,面试官通常会先让你描述解题思路,然后再写代码。因此理解问题本质比直接写代码更重要。
最直观的解法是三层循环遍历所有可能的三元组组合:
java复制for(int i=0; i<n; i++){
for(int j=i+1; j<n; j++){
for(int k=j+1; k<n; k++){
if(nums[i]+nums[j]+nums[k] == 0){
// 处理结果
}
}
}
}
这种解法的时间复杂度是O(n³),对于n=3000的情况,计算量将达到270亿次,显然无法接受。我们需要更高效的算法。
优化的关键在于减少不必要的计算。我们可以采用以下策略:
java复制class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
int n = nums.length;
for(int i=0; i<n-2; i++){
// 跳过重复元素
if(i>0 && nums[i]==nums[i-1]) continue;
int left = i+1, right = n-1;
int target = -nums[i];
while(left < right){
int sum = nums[left] + nums[right];
if(sum == target){
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 跳过重复元素
while(left<right && nums[left]==nums[left+1]) left++;
while(left<right && nums[right]==nums[right-1]) right--;
left++;
right--;
}else if(sum < target){
left++;
}else{
right--;
}
}
}
return result;
}
}
排序处理:
java复制Arrays.sort(nums);
排序是后续所有优化的基础,时间复杂度O(nlogn)。
外层循环:
java复制for(int i=0; i<n-2; i++)
我们固定第一个数,然后在剩余部分寻找另外两个数。
跳过重复元素:
java复制if(i>0 && nums[i]==nums[i-1]) continue;
这避免了重复的三元组出现在结果中。
双指针搜索:
java复制while(left < right){
int sum = nums[left] + nums[right];
if(sum == target){
// 找到解
}else if(sum < target){
left++;
}else{
right--;
}
}
这是两数之和问题的标准解法,时间复杂度O(n)。
这比暴力解法的O(n³)有了显著提升。
最接近的三数之和问题要求找到三个数,使它们的和最接近给定的目标值。与三数之和问题类似,但有以下区别:
我们可以沿用三数之和的框架,但需要做一些调整:
java复制class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int closestSum = nums[0] + nums[1] + nums[2];
int n = nums.length;
for(int i=0; i<n-2; i++){
int left = i+1, right = n-1;
while(left < right){
int sum = nums[i] + nums[left] + nums[right];
if(Math.abs(sum-target) < Math.abs(closestSum-target)){
closestSum = sum;
}
if(sum == target){
return sum; // 直接返回,因为题目保证唯一解
}else if(sum < target){
left++;
}else{
right--;
}
}
}
return closestSum;
}
}
初始值设置:
java复制int closestSum = nums[0] + nums[1] + nums[2];
我们需要一个合理的初始值,这里选择前三个数的和。
更新条件:
java复制if(Math.abs(sum-target) < Math.abs(closestSum-target)){
closestSum = sum;
}
比较当前和与目标值的差距,更新最接近的和。
提前终止:
java复制if(sum == target){
return sum;
}
如果找到完全匹配的和,可以直接返回,因为题目保证唯一解。
在实际编码中,有几个边界条件需要注意:
面试官可能会问:
这类问题在实际中有广泛应用:
双指针技巧之所以有效,是因为:
双指针有多种变种:
双指针适用于:
java复制public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
for(int i=0; i<nums.length-2; i++){
if(i>0 && nums[i]==nums[i-1]) continue;
int left=i+1, right=nums.length-1;
while(left < right){
int sum = nums[i]+nums[left]+nums[right];
if(sum==0){
res.add(Arrays.asList(nums[i],nums[left],nums[right]));
while(left<right && nums[left]==nums[left+1]) left++;
while(left<right && nums[right]==nums[right-1]) right--;
left++; right--;
}else if(sum<0){
left++;
}else{
right--;
}
}
}
return res;
}
在实际面试中,理解算法背后的思想比记住代码更重要。建议多思考为什么这种解法有效,以及如何证明其正确性。双指针技巧是面试中的常客,掌握它可以解决一大类问题。