题目描述

✅ 面试题 16.24. 数对和

image-20260929010409442

题意分析

从数组中找出尽可能多的数对,使每对之和等于 target,每个元素下标最多使用一次。相同数值的不同元素可以分别配对,答案中的数对数值也可以重复;不能把问题变成对数值去重后只找一组答案。

解法:未配对频次表

核心思路

[!blue]

对当前值 x,能配对的数值只有 y = target - x。用 cnt[v] 表示扫描过的元素中,值为 v 且尚未进入答案的数量。读到 x 后,若已有未使用的 y,立即消耗其中一个并输出数对;否则将当前 x 留在表中,等待后续补数。

立即配对不会减少最终数量。对于不同的互补值 x、y,所有配对都只能各取一个,最多组成两类元素数量的较小值。算法不会让两类未配对元素同时剩下,因此扫描结束时必然达到这个上限。若 x = y,则每两个这样的元素组成一对,至多剩下一个,也达到该类的最大对数。

每次先查询历史频次,再决定是否存入当前值,所以配对的是当前下标与此前的另一个下标,不会用同一元素配自己。配对成功后消耗一个历史计数,当前元素也不再入表,保证两者以后都不会复用。

Java 在计数降到零时删除键,因此 containsKey(y) 就等价于还有可用补数;Go 保留零计数,但用 cnt[y] > 0 判断,两种实现维护的是同一个剩余频次含义。

解题步骤

  • 初始化空频次表和答案列表。
  • 顺序读取 x,计算唯一补数 y = target - x。
  • 若历史中还有 y,输出 [x, y] 并消耗一个 y;否则将 cnt[x] 加 1。
  • 继续扫描全部元素后返回结果。剩余元素已经无法互相组成合法数对,无需继续处理。

代码实现

class Solution {
    public List<List<Integer>> pairSums(int[] nums, int target) {
        Map<Integer, Integer> cnt = new HashMap<>();
        List<List<Integer>> answer = new ArrayList<>();

        for (int x : nums) {
            int y = target - x;

            if (cnt.containsKey(y)) {
                answer.add(List.of(x, y));

                if (cnt.merge(y, -1, Integer::sum) == 0) {
                    cnt.remove(y);
                }
            } else {
                cnt.merge(x, 1, Integer::sum);
            }
        }

        return answer;
    }
}
func pairSums(nums []int, target int) (answer [][]int) {
    cnt := map[int]int{}
    for _, x := range nums {
        y := target - x
        if cnt[y] > 0 {
            cnt[y]--
            answer = append(answer, []int{
                x,
                y,
            })
        } else {
            cnt[x]++
        }
    }
    return
}

复杂度分析

  • 时间复杂度:期望 $O(n)$。每个元素只查询、更新常数次哈希表,最多输出 n/2 对。
  • 空间复杂度:$O(n)$。未配对频次表最坏保存线性个不同值,返回结果也最多包含线性个元素。

关键点总结

[!green]

  • 补数唯一,使每个数值只参与一类配对,立即匹配就能达到最大对数。
  • 频次保存的是剩余可用元素数量,不是是否曾经出现。
  • 查询历史发生在当前值入表之前,补数等于自身时也不会复用当前下标。
  • 算法只读输入数组,不需要排序。

易错点总结

[!yellow]

  • 只用集合记录数值,会丢失相同值还有多少个可用元素的信息。
  • 配对后不减少频次,会把同一个历史下标反复加入答案。
  • 先将当前值加入表再查补数,可能在 target = 2 * x 时让元素与自己配对。
  • 找到第一对就返回,会漏掉其余仍能组成的数对。
  • Java 使用键存在性判断时必须删除零计数键,否则会把已经消耗完的补数当作仍然可用。

相似题目

题目 难度 关联与区别
1. 两数之和 简单 原题只找一对且通常返回下标,本题要持续消耗频次并输出不重用元素的全部数对。
167. 两数之和 II - 输入有序数组 中等 排序后可用双指针找互补值,本题重复命中后必须同时消耗两个位置。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/68046313
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!