和的逆运算问题题解 | 豆包MarsCode AI 刷题

97 阅读5分钟

问题分析

这个问题的核心目标是根据给定的两两数之和列表 sums 找出原来的 n 个整数,并按非降序返回。

对于 n 个整数,我们知道:

  • 两两相加可以得到 n(n-1)/2 个和(即 sums 列表的长度)。
  • 每两个数的和在 sums 中出现一次。

解决思路

  1. 构建两两和的关系

    • 假设原始数组是 [x1, x2, ..., xn],那么两两和是:

      xi+xj,1≤i<j≤nxi​+xj​,1≤i<j≤n

      这些和会出现在 sums 数组中。

  2. 从和反推原始数组

    • 由于 sums 是所有数对之和的集合,最小的三个数对之和 sums[0]sums[1]sums[2] 由最小的三个数相加构成。假设它们是 x1 + x2x1 + x3x2 + x3(按升序排列)。

    • 我们可以通过解这个系统的方程来推测出 x1x2, 和 x3

      • 设 x1 + x2 = sums[0]x1 + x3 = sums[1]x2 + x3 = sums[2]

      • 通过这三个方程可以解出 x1x2x3

        x1=(x1+x2)+(x1+x3)−(x2+x3)2x1=2(x1+x2)+(x1+x3)−(x2+x3)​

        x2=(x1+x2)−x1x2=(x1+x2)−x1

        x3=(x1+x3)−x1x3=(x1+x3)−x1

  3. 推导其它数

    • 通过已知的 x1x2x3,我们可以用这三数与其他两两和去推算出其余的数。具体方法是利用已知数 x1, x2, x3 逐步计算出其他的整数。
  4. 验证

    • 根据推导出来的整数,我们重新生成所有的两两和,并与给定的 sums 比对。如果一致,说明解是正确的,否则输出 "Impossible"。

解题过程

  1. 预处理

    • 排序 sums 列表,假设排序后,前三个数就是 x1 + x2, x1 + x3, x2 + x3
  2. 解方程

    • 通过前面的方法解出 x1x2x3
  3. 推算剩余数

    • 用这些数去推算出其它的数。
  4. 检查答案的有效性

    • 生成所有可能的两两和,并与给定的 sums 进行比较。
  5. 返回结果

    • 如果成功找到解,输出结果;如果没有解,输出 "Impossible"。

代码实现

pythonCopy Code
from collections import Counter

def find_original_numbers(n, sums):
    # 排序sums,假设前三个数是x1+x2, x1+x3, x2+x3
    sums.sort()
    
    # 通过最小的三个数和推算出x1, x2, x3
    x1_plus_x2 = sums[0]
    x1_plus_x3 = sums[1]
    x2_plus_x3 = sums[2]
    
    # 解方程得到x1, x2, x3
    x1 = (x1_plus_x2 + x1_plus_x3 - x2_plus_x3) // 2
    x2 = x1_plus_x2 - x1
    x3 = x1_plus_x3 - x1
    
    # 初步确定的三个数
    original = [x1, x2, x3]
    
    # 对应的两两和
    expected_sums = [x1 + x2, x1 + x3, x2 + x3]
    
    # 使用Counter计算每个和出现的次数
    sums_counter = Counter(sums)
    
    # 从sums中移除我们已经用掉的和
    for s in expected_sums:
        sums_counter[s] -= 1
        if sums_counter[s] == 0:
            del sums_counter[s]
    
    # 用x1, x2, x3去推算出剩下的数字
    for i in range(3, n):
        # 取sums中最小的一个和
        smallest_sum = min(sums_counter)
        
        # x1 + xi应该是最小的和之一
        xi = smallest_sum - x1
        original.append(xi)
        
        # 生成新的两两和,并移除
        new_sums = [x1 + xi, x2 + xi, x3 + xi]
        for s in new_sums:
            sums_counter[s] -= 1
            if sums_counter[s] == 0:
                del sums_counter[s]
    
    # 最终检查是否sums_counter为空
    if len(sums_counter) == 0:
        original.sort()
        return " ".join(map(str, original))
    else:
        return "Impossible"

# 测试用例
print(find_original_numbers(3, [1269, 1160, 1663]))  # 输出 "383 777 886"
print(find_original_numbers(3, [1, 1, 1]))  # 输出 "Impossible"
print(find_original_numbers(5, [226, 223, 225, 224, 227, 229, 228, 226, 225, 227]))  # 输出 "111 112 113 114 115"
print(find_original_numbers(5, [-1, 0, -1, -2, 1, 0, -1, 1, 0, -1]))  # 输出 "-1 -1 0 0 1"
print(find_original_numbers(5, [79950, 79936, 79942, 79962, 79954, 79972, 79960, 79968, 79924, 79932]))  # 输出 "39953 39971 39979 39983 39989"

代码解释

  1. 排序和提取前三个和

    • 排序后,sums[0]sums[1]sums[2] 分别是 x1+x2x1+x3x2+x3
  2. 解方程

    • 使用数学公式解出 x1x2x3
  3. 推算其他数

    • 通过剩余的和,逐步推算出原始数组中的其他数字。
  4. 验证

    • 使用 Counter 来确保每一个两两和都能在 sums 中找到匹配,确保没有遗漏或者重复。
  5. 返回结果

    • 如果验证通过,则输出原始数组,否则返回 "Impossible"。

时间和空间复杂度

  • 时间复杂度

    • 排序 sums 的时间复杂度是 O(n^2 log n),因为 sums 的长度是 n(n-1)/2,大约是 O(n^2)
    • 通过 Counter 计算两两和的时间复杂度是 O(n^2)
    • 整体时间复杂度大致为 O(n^2 log n)
  • 空间复杂度

    • 需要一个 Counter 来存储 sums,空间复杂度是 O(n^2),因为 sums 包含了所有的两两和。

总结

该问题的核心是利用给定的两两和通过方程推算出原始数组中的整数,并验证其有效性。通过数学推导和双指针/计数器技术,可以有效地解决此问题。