题目解析:三数之和问题| 豆包MarsCode AI 刷题

74 阅读4分钟

题目解析

根据题目要求,我们需要在一个整数数组 arr 中找到三元组 (i, j, k),使得满足以下条件:

  1. i < j < k (即三元组索引是递增的)。
  2. arr[i] + arr[j] + arr[k] == target

约束条件

  • 数组可能包含重复元素。
  • 结果需要对 109+710^9 + 7 取模。

输入输出示例分析

  • 输入
    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 组。


解题思路

我们需要找到所有满足条件的三元组。由于数组可能包含重复元素,直接暴力三重循环会导致极大计算量,所以需要优化。

优化思路

  1. 统计元素频率
    使用哈希表统计数组中各个元素的出现次数。例如,对于 arr = [1, 1, 2, 2, 3, 3],统计为 {1:2, 2:2, 3:2}

  2. 去重并排序
    将数组的元素去重并排序后得到一个唯一元素的有序数组 keys。这样可以方便地确定三元组的组合方式。

  3. 三重循环的优化

    • 第一层 i:遍历 keys 的第一个元素。
    • 第二层 j:从 i 开始遍历 keys 的第二个元素。
    • 第三层 k:计算 k = target - keys[i] - keys[j],并检查 k 是否符合条件。
  4. 分类讨论三元组情况
    根据 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]]
      
  5. 取模运算
    每次累加结果时,对 109+710^9 + 7 取模,避免溢出。


代码详解

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);
    }
}

代码解读

  1. 统计元素频率
    使用 HashMap 统计每个元素的出现次数,时间复杂度为 O(n)

  2. 排序并去重
    keyscount 的键集合,排序后方便处理。时间复杂度为 O(m log m),其中 m 是数组中不同元素的个数。

  3. 遍历三元组
    使用双重循环加查找的方式,避免了三重循环的暴力解法。时间复杂度约为 O(m^2)

  4. 组合计算
    按照分类讨论,分别计算三种情况的组合数。

  5. 取模运算
    使用 MOD 避免溢出。


复杂度分析

  • 时间复杂度

    • 统计频率:O(n)O(n)
    • 排序:O(mlogm)O(m log m)
    • 遍历三元组:O(m2)O(m^2)
      综合复杂度为 O(n+mlogm+m2)O(n + m log m + m^2),其中 n 为数组长度,m 为不同元素的个数。
  • 空间复杂度
    使用了 HashMap 和排序数组,空间复杂度为 O(m)O(m)


思考与总结

  1. 暴力解法的局限性
    如果直接使用三重循环,对于 arr 长度为 10410^4 的情况,复杂度为 O(n3)O(n^3),无法接受。通过优化到 O(m2)O(m^2),可以解决大部分问题。

  2. 为什么使用哈希表统计频率
    统计频率后可以直接通过组合数公式简化计算,避免重复计算和冗余遍历。

  3. 分类讨论的重要性
    通过将三种情况拆分处理,能够准确计算每种三元组的数量,避免遗漏或重复。

  4. 适用场景
    此方法适合解决类似三数之和、多数之和的问题,尤其是数组中有重复元素时。

通过本题的分析和实现,我们可以深刻理解如何优化组合类问题,并掌握分治思想与高效查找技术的应用。