题目解析
根据题目要求,我们需要在一个整数数组 arr 中找到三元组 (i, j, k),使得满足以下条件:
i < j < k(即三元组索引是递增的)。arr[i] + arr[j] + arr[k] == target。
约束条件:
- 数组可能包含重复元素。
- 结果需要对 取模。
输入输出示例分析
-
输入:
arr = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5], target = 8
输出:20
解释:满足条件的三元组有(1, 2, 5)、(1, 3, 4)等,共 20 组。 -
输入:
arr = [2, 2, 2, 2], target = 6
输出:4
解释:三元组(2, 2, 2)满足条件,4 组。 -
输入:
arr = [1, 2, 3, 4, 5], target = 9
输出:2
解释:三元组(1, 3, 5)和(2, 3, 4)满足条件。 -
输入:
arr = [1, 1, 1, 1], target = 3
输出:4
解释:三元组(1, 1, 1)满足条件,4 组。
解题思路
我们需要找到所有满足条件的三元组。由于数组可能包含重复元素,直接暴力三重循环会导致极大计算量,所以需要优化。
优化思路
-
统计元素频率
使用哈希表统计数组中各个元素的出现次数。例如,对于arr = [1, 1, 2, 2, 3, 3],统计为{1:2, 2:2, 3:2}。 -
去重并排序
将数组的元素去重并排序后得到一个唯一元素的有序数组keys。这样可以方便地确定三元组的组合方式。 -
三重循环的优化
- 第一层
i:遍历keys的第一个元素。 - 第二层
j:从i开始遍历keys的第二个元素。 - 第三层
k:计算k = target - keys[i] - keys[j],并检查k是否符合条件。
- 第一层
-
分类讨论三元组情况
根据keys[i]、keys[j]和keys[k]是否相等,计算三元组的组合数量。- 所有三个数都相等:
如果keys[i] == keys[j] == keys[k],则组合数为:C(count[keys[i]], 3) = count[keys[i]] * (count[keys[i]] - 1) * (count[keys[i]] - 2) / 6 - 两个数相等,一个不同:
如果keys[i] == keys[j] != keys[k],则组合数为:C(count[keys[i]], 2) * count[keys[k]] - 三个数均不相等:
如果keys[i] != keys[j] != keys[k],则组合数为:count[keys[i]] * count[keys[j]] * count[keys[k]]
- 所有三个数都相等:
-
取模运算
每次累加结果时,对 取模,避免溢出。
代码详解
import java.util.HashMap;
import java.util.Map;
public class Main {
public static int solution(int[] arr, int t) {
final int MOD = 1000000007;
Map<Integer, Integer> count = new HashMap<>();
for (int num : arr) {
count.put(num, count.getOrDefault(num, 0) + 1);
}
int[] keys = count.keySet().stream().mapToInt(i -> i).sorted().toArray();
int n = keys.length;
long result = 0;
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
int k = t - keys[i] - keys[j];
if (k < keys[j]) break;
if (count.containsKey(k)) {
if (i == j && j == indexOf(keys, k)) {
result += (long) count.get(keys[i]) * (count.get(keys[i]) - 1) * (count.get(keys[i]) - 2) / 6;
} else if (i == j) {
result += (long) count.get(keys[i]) * (count.get(keys[i]) - 1) / 2 * count.get(k);
} else if (j == indexOf(keys, k)) {
result += (long) count.get(keys[i]) * count.get(keys[j]) * (count.get(keys[j]) - 1) / 2;
} else {
result += (long) count.get(keys[i]) * count.get(keys[j]) * count.get(k);
}
result %= MOD;
}
}
}
return (int) result;
}
private static int indexOf(int[] arr, int key) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == key) return i;
}
return -1;
}
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);
}
}
代码解读
-
统计元素频率
使用HashMap统计每个元素的出现次数,时间复杂度为O(n)。 -
排序并去重
keys是count的键集合,排序后方便处理。时间复杂度为O(m log m),其中m是数组中不同元素的个数。 -
遍历三元组
使用双重循环加查找的方式,避免了三重循环的暴力解法。时间复杂度约为O(m^2)。 -
组合计算
按照分类讨论,分别计算三种情况的组合数。 -
取模运算
使用MOD避免溢出。
复杂度分析
-
时间复杂度:
- 统计频率:
- 排序:
- 遍历三元组:
综合复杂度为 ,其中n为数组长度,m为不同元素的个数。
-
空间复杂度:
使用了HashMap和排序数组,空间复杂度为 。
思考与总结
-
暴力解法的局限性
如果直接使用三重循环,对于arr长度为 的情况,复杂度为 ,无法接受。通过优化到 ,可以解决大部分问题。 -
为什么使用哈希表统计频率
统计频率后可以直接通过组合数公式简化计算,避免重复计算和冗余遍历。 -
分类讨论的重要性
通过将三种情况拆分处理,能够准确计算每种三元组的数量,避免遗漏或重复。 -
适用场景
此方法适合解决类似三数之和、多数之和的问题,尤其是数组中有重复元素时。
通过本题的分析和实现,我们可以深刻理解如何优化组合类问题,并掌握分治思想与高效查找技术的应用。