题目描述

✅ 面试题 16.21. 交换和

image-20260929010356353

题意分析

从 array1 选一个数 a、从 array2 选一个数 b 交换,使交换后两个数组元素和相等;找到任意一组即可,无解返回空数组。

朴素做法枚举所有 (a,b),每对再验证,时间是 O(nm)。真正有用的信息只有两边总和。设原和为 S1、S2,交换后满足 S1-a+b = S2-b+a,整理得到:

a - b = (S1 - S2) / 2。

因此总和差必须是偶数;一旦固定 a,所需的 b 唯一等于 a - (S1-S2)/2。把第二个数组放进哈希集合即可把枚举一对数降成枚举一个数。

解法:代数化简 + 哈希集合

核心思路

[!blue]

先用 64 位整数求两边总和与差 diff = S1 - S2。交换 a、b 会让第一边减少 a - b、第二边增加同样的量,因此原总和差必须等于 2 * (a - b)。差为奇数就不可能通过整数交换消除;这个判断也适用于负差值。

差为偶数时,令 half = diff / 2,代码直接复用 diff 保存这个值。固定第一个数组中的 a 后,唯一可能的交换对象是 b = a - half。把第二个数组的元素加入哈希集合,就能快速判断这个对象是否真实存在,无需再枚举第二个数组的所有位置。

枚举每个 a 不会漏解:任何合法交换都满足推导出的等式,所以一定会在对应的 a 处查到 b;反过来,找到集合中的 b 就能直接代回等式,使两边总和相等。只交换一个元素,因此第二个数组中的重复值只需保留“是否存在”,不需要计数。

候选值的运算也使用 64 位。Java 集合存放 int,只有候选落在其范围内才能转换并查找,避免窄化后误命中;Go 集合直接保存 int64,命中后才转回原元素类型。若两边原总和相等,仍需找到公共值完成一次等值交换,不能直接认定存在可返回的数对。

解题步骤

  • 扫描两个数组求 S1、S2,同时把 array2 的值加入集合。
  • 计算 diff=S1-S2;若 diff 为奇数,返回空数组。
  • 令 half=diff/2。
  • 枚举 array1 的 a,计算唯一候选 b=a-half。
  • b 在整数范围内且存在于集合时返回 [a,b];扫描结束仍未命中则无解。

代码实现

class Solution {
    public int[] findSwapValues(int[] array1, int[] array2) {
        long s1 = 0;
        long s2 = 0;
        Set<Integer> s = new HashSet<>();

        for (int x : array1) {
            s1 += x;
        }

        for (int x : array2) {
            s2 += x;
            s.add(x);
        }

        long diff = s1 - s2;

        if (diff % 2 != 0) {
            return new int[0];
        }

        diff /= 2;

        for (int a : array1) {
            long candidate = (long) a - diff;

            if (candidate >= Integer.MIN_VALUE
                    && candidate <= Integer.MAX_VALUE
                    && s.contains((int) candidate)) {
                return new int[] {
                    a,
                    (int) candidate
                };
            }
        }

        return new int[0];
    }
}
func findSwapValues(array1 []int, array2 []int) []int {
    s1, s2 := int64(0), int64(0)
    s := map[int64]bool{}
    for _, a := range array1 {
        s1 += int64(a)
    }
    for _, b := range array2 {
        s2 += int64(b)
        s[int64(b)] = true
    }
    diff := s1 - s2
    if (diff & 1) == 1 {
        return []int{}
    }
    diff >>= 1
    for _, a := range array1 {
        if b := int64(a) - diff; s[b] {
            return []int{
                a,
                int(b),
            }
        }
    }
    return []int{}
}

复杂度分析

  • 时间复杂度:期望 O(n+m),分别扫描两个数组,哈希查询均摊 O(1)。
  • 空间复杂度:O(m),集合最多保存第二个数组的 m 个不同值。

关键点总结

[!green]

  • 先写交换后的等式再移项,符号自然得到 a-b=(S1-S2)/2,比凭感觉猜差值稳。
  • 奇偶性是零成本剪枝:总和差为奇数时整数交换不可能补平。
  • 总和与中间差使用宽整数,数组元素是 int 不代表总和也安全。
  • 每个候选都同时满足差值等式与跨数组存在条件,返回后即可执行实际交换。

易错点总结

[!yellow]

  • 固定 a 后需要 b=a-half,不能把符号写反。
  • 总和差为奇数时无解,不能直接整数除二后继续匹配。
  • 两边总和及差值使用 64 位;溢出会改变需要查找的候选值。
  • Java 集合存 int,候选 long 需检查范围再转换;Go 集合直接存 int64,只有命中已存在的原元素时才转换返回。
  • 集合必须来自另一数组,才能保证实际执行跨数组交换。

相似题目

题目 难度 关联与区别
888. 公平的糖果交换 简单 同样由交换前后总和相等推导差值,原题是糖果数量,本题支持一般整数数组。
1. 两数之和 简单 同样固定一个元素后查询唯一互补值,本题补数由两数组总和差决定。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/22540612
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!