Swapping Pair Make Sum Equal Problem in Java

2025年5月6日 | 阅读 9 分钟

这是 Google、Amazon、TCS、Accenture、Flipkart 等顶级 IT 公司面试中经常遇到的一个问题。通过解决这个问题,可以考察面试者的逻辑能力、批判性思维和解决问题的能力。因此,在本节中,我们将探讨**交换对使和相等问题**的各种方法和逻辑。我们还将创建相应的 Java 程序。

问题陈述

**交换对使和相等问题**涉及从两个 数组中各找一个元素,交换它们后使两个数组的总和相等。这需要确定目标差值,并使用哈希或基于排序的技术来高效地搜索有效对。

示例

输入

数组 1: [5, 7, 4, 6]

数组 2: [1, 2, 3, 8]

输出

交换对: (5, 1)

解释

将数组 1 中的 5 与数组 2 中的 1 交换后,新的总和变得相等。最初,数组 1 的总和为 22,数组 2 的总和为 14,差值为 8。交换后,数组 1 的总和减少 4,数组 2 的总和增加 4,两者的总和都平衡在 18。

方法 1: 使用 HashSet

算法

步骤 1: 计算总和: 分别计算数组 1 (sum1) 和数组 2 (sum2) 中元素的总和。这有助于我们了解两个数组当前的总和差值。

步骤 1.1: 检查是否提前终止: 在继续之前,重要的是检查总和差值是否为偶数。如果 sum1 - sum2 是奇数,则意味着不存在有效的对,因为从一个数组中交换元素与另一个数组中的元素不会平衡总和。此检查允许提前终止,避免不必要的计算。如果差值为奇数,则返回 null,因为不存在有效的解决方案。

步骤 2: 确定差值: 计算两个数组的总和之差

  • 差值 = sum1 - sum2

如果此差值为奇数,则无法平均分配,因为整数交换无法实现分数变化。在这种情况下返回 null。

否则,计算交换对的目标差值

  • 目标 = 差值 / 2

目标值告诉我们两个交换的元素之间的差值应该为多少。

步骤 3: 为数组 2 准备快速查找: 为了快速查找潜在对的存在,将数组 2 的所有元素存储在 HashSet 中。这允许在搜索有效对时进行常数时间查找。

步骤 3.1: 处理空数组: 准备好数组 2 的 HashSet 后,必须检查数组 1 或数组 2 是否为空。如果其中一个数组为空,则无法进行交换,我们应立即返回 null。此步骤确保我们避免不必要的处理并高效地处理边缘情况。

步骤 4: 搜索交换对: 遍历数组 1 中的每个元素 a。对于每个元素 a,计算数组 2 中需要的值 b,以平衡总和

  • b = a - 目标

检查 b 是否存在于 HashSet 中。如果存在,则 a 和 b 构成一个可以交换的有效对。

步骤 5: 返回结果: 如果找到有效的对 (a, b),则将其作为结果返回。如果在检查完数组 1 中的所有元素后未找到这样的对,则返回 null。

步骤 6: 如果未找到对,则返回 null: 如果在检查完数组 1 的所有元素后仍未找到有效的对,则返回 null,表示无法通过交换两个数组中的元素来使总和相等。此步骤确保算法能够处理不存在解决方案的情况并提供明确的结果。

文件名: SwapPairEqualSum.java

输出

 
Swap pair: (5, 1)   

复杂度分析

时间复杂度

该算法的时间复杂度为 O(m + n),其中 m 和 n 分别是两个数组的大小。计算总和需要 O(m + n),构建 HashSet 需要 O(n),查找对需要 O(m)。它通过避免嵌套迭代来确保高效的性能。

空间复杂度

程序的空间复杂度为 O(n),其中 n 是第二个数组的大小。这是因为将数组 2 的元素存储在 HashSet 中以进行高效查找。除了 HashSet 之外,该算法使用了恒定的额外空间,因此总体上是空间高效的。

方法 2: 使用双指针技术

算法

步骤 1: 计算两个数组的总和: 首先,计算两个数组中元素的总和。我们将第一个数组的总和称为 sum1,第二个数组的总和称为 sum2。这些总和之间的差值 (diff = sum1 - sum2) 告诉我们数组的失衡程度。如果此差值为奇数,则不可能平均分配差值,因此我们返回 null。如果差值为偶数,我们计算目标 = diff / 2,这将指导所需的交换。

步骤 2: 对两个数组进行排序: 对数组进行排序可以更轻松地应用双指针技术。通过将 array1 按升序排序,将 array2 按降序排序,我们可以快速找到正确的对。

步骤 2.1: 处理边缘情况: 在步骤 2 对两个数组进行排序后,在继续双指针搜索之前,处理边缘情况很重要。具体来说,您需要处理一个或两个数组可能为空,或者数组大小太小而无法形成有效对的情况。

步骤 3: 双指针搜索: 初始化两个指针:一个指针 (i) 指向 array1 的开头,另一个指针 (j) 指向 array2 的末尾。目的是找到两个元素,一个来自 array1 (a),一个来自 array2 (b),使得它们之间的差值 (a - b) 等于目标。

如果 a - b 等于目标,则返回此对作为解决方案。

如果 a - b 大于目标,则表示我们需要 array1 中的一个较小值,因此我们增加指针 i 以移动到较大的元素。

如果 a - b 小于目标,则表示我们需要 array2 中的一个较大值,因此我们减小指针 j 以移动到较小的元素。

步骤 4: 返回结果: 如果两个指针在未找到有效对的情况下交叉,则返回 null。否则,找到的对就是解决方案。

步骤 5: 验证交换: 找到一对元素后,验证交换它们是否会平衡两个数组的总和很重要。

重新计算总和: 执行交换后,再次计算两个数组的总和。如果总和现在相等,则交换有效并解决了问题。

检查意外更改: 确保交换没有导致数组发生任何意外更改,例如破坏顺序或意外修改其他元素。

文件名: SwapPairEqualSum.java

输出

 
Checking: (4, 8) with difference: -4
Checking: (4, 3) with difference: 1
Checking: (4, 2) with difference: 2
Checking: (4, 1) with difference: 3
No valid swap pair found.   

复杂度分析

时间复杂度

此方法的时间复杂度为 O (n log n + m log m),其中 n 和 m 分别是 array1 和 array2 的大小。对两个数组进行排序需要 O (n log n) 和 O (m log m)。双指针搜索以 O(n + m) 的时间复杂度遍历数组,使该方法整体上高效。

空间复杂度

假设排序是就地执行的,则程序的空间复杂度为 O (1)。该算法除了输入数组外,不使用任何额外的 数据结构,只使用几个整数 变量和指针进行计算。因此,它以恒定的额外空间运行,使其在内存使用方面高效。

性质

偶数总和差: 此问题的一个基本属性是,两个数组的总和之差必须为偶数,才能存在有效的交换。如果差值为奇数,则无法进行有效的交换,可以提前终止解决方案。

目标差值: 一旦确认总和差值为偶数,将其除以二即可计算目标差值。此目标表示为了使总和相等,两个交换的元素需要具有的差值。

对的存在: 问题的核心在于找到两个元素,一个来自每个数组,使得它们的差值与目标匹配。这可以通过基于哈希的查找或双指针技术来实现,具体取决于所采用的方法。

搜索效率: 通过利用 HashSet(用于常数时间查找)或排序后跟双指针搜索,可以高效地解决该问题。虽然基于哈希的方法可确保线性时间复杂度,但双指针方法会引入排序,从而增加了时间复杂度中的对数分量。

可伸缩性: 该解决方案高效且可伸缩,适用于各种大小的数组。两种方法都可以处理较大的输入,尽管空间复杂度有所不同。哈希需要额外的内存来存储其中一个数组的元素,而双指针方法除了输入数组外,使用恒定的空间。

优点

  • 效率: HashSet 和双指针方法都避免了穷举检查,使其比暴力方法更快、更实用。
  • 可伸缩性: 这些方法可以有效地处理大型数组,即使输入大小很大也能确保良好的性能。
  • 简洁性: 该逻辑易于遵循,使用了诸如求和、排序和查找之类的基本操作。
  • 灵活性: 不同的技术可以很好地适应各种场景,例如未排序的数组(HashSet)或已排序的数组(双指针)。
  • 低内存使用: 双指针方法几乎不需要额外的空间,非常适合受限环境。

缺点

  • 排序开销: 双指针方法需要对两个数组进行排序,其时间复杂度为 O (n log n) 和 O(m log m),这增加了额外的计算成本,尤其对于大型数组。
  • 内存使用: 在第一种方法中使用 HashSet 需要额外的 内存来存储数组 2 的元素,这在处理大型数据集时可能会效率低下。
  • 在不存在有效对时效率低下: 如果找不到有效的对,算法仍会处理所有元素,这可能效率低下,因为会发生不必要的迭代。
  • 对边缘情况敏感: 该解决方案假定输入结构良好,并且可能难以处理边缘情况,例如没有可能交换的数组或包含可能导致溢出的非常大的值。
  • 仅限于整数: 当前解决方案主要适用于 整数,需要进行重大更改才能处理其他数据类型或约束。

下一主题旅行商问题