题目描述

✅ 170. 两数之和 III - 数据结构设计

题意分析

容器支持不断添加整数,以及查询当前是否存在两个数之和等于指定目标。两个数必须来自两次不同的添加,但数值可以相同。查询只判断是否存在,不需要返回下标,也不会消耗已经添加的数字。

解法:哈希频次 + 补数查询

核心思路

[!blue]
按数值保存频次,枚举一个加数后唯一确定另一个。 countMap[x] 表示数字 x 已添加多少次。对于目标 value 和已有数字 x,另一个加数必须是 value-x,因此只需查一次哈希表,无需再遍历第二个加数。

若补数不同于 x,两个值都存在就对应两次不同的添加,可以配对;若补数等于 x,查到这个键只能说明至少有一份,必须进一步要求频次不少于 2,才能避免重复使用同一个元素。

任意合法数对中的一个值都会在枚举中出现,它的另一个值又恰好是查询到的补数,所以全部不同值检查完仍未命中时,才能确认没有答案。重复添加只改变频次,不需要保存重复键;重复查询也只读取频次,容器状态保持不变。

解题步骤

  1. 构造时初始化空频次表。
  2. add(number) 将对应频次加一,首次出现时从 0 开始累加。
  3. find(value) 遍历每个不同值,计算补数,并分别检查异值存在性或同值的两份要求。
  4. 找到任一合法配对立即返回 true,全部检查完才返回 false。

空容器和只有一份数字的容器都无法组成数对;两份相同数字则可以配成其两倍。补数使用 long 或 int64 计算,先转换到宽整数再做减法,避免目标与已有值相减时发生窄整数溢出。

代码实现

class TwoSum {

    // 用计数而非布尔标记,才能判定「两个相同数值」的配对。
    private Map<Long, Integer> countMap;

    public TwoSum() {
        countMap = new HashMap<>();
    }

    public void add(int number) {
        long key = number;

        countMap.put(key, countMap.getOrDefault(key, 0) + 1);
    }

    public boolean find(int value) {
        for (Map.Entry<Long, Integer> entry : countMap.entrySet()) {
            long num = entry.getKey();
            // 用宽整数计算补数,同值需要两份,异值只需确认存在。
            long complement = (long) value - num;

            if (complement == num) {
                // 自我配对必须有两份。
                if (entry.getValue() >= 2) {
                    return true;
                }
            } else {
                if (countMap.containsKey(complement)) {
                    return true;
                }
            }
        }

        return false;
    }
}
type TwoSum struct {
    // 用计数而非布尔标记,才能判定「两个相同数值」的配对。
    countMap map[int64]int
}

func Constructor() TwoSum {
    return TwoSum{countMap: make(map[int64]int)}
}

func (t *TwoSum) Add(number int) {
    t.countMap[int64(number)]++
}

func (t *TwoSum) Find(value int) bool {
    for num, cnt := range t.countMap {
        // 用宽整数计算补数,同值需要两份,异值只需确认存在。
        complement := int64(value) - num

        if complement == num {
            // 自我配对必须有两份。
            if cnt >= 2 {
                return true
            }
        } else {
            if _, ok := t.countMap[complement]; ok {
                return true
            }
        }
    }
    return false
}

复杂度分析

  • 时间复杂度:add 期望 $O(1)$;find 期望 $O(d+1)$,d 为当前不同值数量。
  • 空间复杂度:$O(d+1)$,相同值压缩为计数。

关键点总结

[!green]

  • 同值配对需要两份,异值配对只需分别存在。
  • 补数由目标唯一确定,避免枚举全部数对。
  • 查询失败只能在所有不同值检查完之后确定。

易错点总结

[!yellow]

  • 用集合只记录存在性:无法区分一份与两份相同值。
  • add 总是将计数设为一:重复添加的信息丢失。
  • 补数等于自身时直接跳过:会漏掉两份相同值的合法情况。
  • 一次查找未命中就返回 false:其他值仍可能组成目标。

相似题目

题目 难度 关联与区别
1. 两数之和 简单 原题只查一次目标和,本题支持多次add与find,需要长期维护频次并处理同值配对。
167. 两数之和 II - 输入有序数组 中等 如果数据可批量排序,可通过两端指针查询;动态插入场景要权衡维护成本。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2023/74855848
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!