题目指路-->三数之和问题 - MarsCode
问题描述
小U有一个整数数组 arr,他希望找到其中三个元素 i, j, k 满足条件 i < j < k 且 arr[i] + arr[j] + arr[k] == target。由于可能存在大量的元组,结果需要对 10^9 + 7 取模。
例如:当 arr = [1,1,2,2,3,3,4,4,5,5] 且 t = 8 时,有多个元组满足条件,你需要返回这些元组的数量。
解题思路
根据题目得知,我们需要遍历整数数组 arr找出所有三数之和为目标值的可能性。除了暴力搜索之外,我们还可以通过双指针算法解决这个问题。
双指针是一种优化搜索范围的经典算法,特别适用于有序数组中的问题。通过设置两个指针分别从两端开始(或一端和另一端),我们可以根据条件动态调整指针的位置,避免冗余的暴力搜索。
核心思想:通过排序将数组变为有序,利用指针的位置关系快速缩小范围,从而将复杂度从 O(n3)O(n^3)O(n3) 降低到 O(n2)O(n^2)O(n2)。
-
数组排序:先对数组进行升序排序,满足使用双指针的条件。
-
三重循环的优化:
- 固定第一个元素
arr[i],从 i+1i+1i+1 到末尾范围内寻找满足条件的另外两个数。 - 使用双指针,一个从左边起 (
l = i + 1),另一个从右边起 (r = len - 1)。
- 固定第一个元素
-
双指针扫描:
-
计算当前三数之和
sum = arr[i] + arr[l] + arr[r]。 -
根据
sum和target的关系调整指针:-
若
sum < target,左指针右移; -
若
sum > target,右指针左移; -
若
sum == target,统计左右相同数字的组合数:- 如果左右指针数字相同,计算从
l到r的组合数。 - 如果左右指针数字不同,统计左边和右边的相同数字数量,并计算组合数。
- 如果左右指针数字相同,计算从
-
-
更新答案。
-
-
取模:在结果返回之前对 109+710^9 + 7109+7 取模。
时间复杂度:O(n^2)
空间复杂度: O(1)
详细代码及注释:
import java.util.*;
public class Main {
public static int solution(int[] arr, int t) {
int MOD = 1000000007;
long ans = 0; // 防止溢出
int len=arr.length;
//排序数组
Arrays.sort(arr);
for (int i=0; i<len-2; i++) {
int l=i+1; // 左指针
int r=len-1; // 右指针
while (l<r) {
int sum=arr[i]+arr[l]+arr[r];
if (sum == t) {
if (arr[l] == arr[r]) {
ans += ((r-l+1) * (r-l) / 2); // 计算组合数
break; // 直接跳出
}
else {
int lCount=1, rCount=1;
// 统计左指针相同元素数量
while (l+1<r&&arr[l]==arr[l+1]) {
l++;
lCount++;
}
// 统计右指针相同元素数量
while (r-1>l&&arr[r]==arr[r - 1]){
r--;
rCount++;
}
ans+=(lCount * rCount); // 组合数
l++;
r--;
}
}
else if(sum < t)l++;
else r--;
}
}
return (int) (ans % MOD); // 对结果取模
}
public static void main(String[] args) {
System.out.println(solution(new int[]{1, 1, 2, 2, 3, 3, 4, 4, 5, 5}, 8) == 20);
System.out.println(solution(new int[]{2, 2, 2, 2}, 6) == 4);
System.out.println(solution(new int[]{1, 2, 3, 4, 5}, 9) == 2);
System.out.println(solution(new int[]{1, 1, 1, 1}, 3) == 4);
}
}
双指针算法的注意事项:
1. 确保数组有序性
- 双指针通常需要有序的数组或字符串(升序或降序)。如果数组无序,直接应用可能会出现各种问题。
2. 左右指针的初始化
-
常见方式:
- 左右两端型:
left = 0,right = arr.length - 1。 - 左中右型(如三数求和问题):外层固定一个元素,双指针在其右侧范围内操作。
- 左右两端型:
-
注意事项:
- 确保指针初始化的范围合理,避免直接越界。
- 如果问题涉及多种方向的搜索,需要明确各自的范围。
3. 边界条件处理
-
问题:指针移动可能导致越界或遗漏特定情况。
-
解决方法:
-
明确
while (left < right)的终止条件,避免指针重合或跳过目标元素。 -
在更新指针位置时,确保新值在合法范围内。
-