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

102 阅读7分钟

问题描述

n 个整数两两相加可以得到 n(n - 1) / 2 个和。我们的目标是:根据这些和找出原来的 n 个整数。

按非降序排序返回这 n 个数,如果无解,输出 "Impossible"。


问题分析

这个问题要求我们从给定的 n 个整数的和 sums 中恢复出原始的 n 个整数。已知的是,sums 数组包含了原始整数的所有可能的两两和,数组的大小为 n(n-1)/2,并且这些和是两两相加得到的。我们的目标是根据这些和找出原来的整数,并按非降序返回。如果无法恢复出原始整数,则返回 "Impossible"。

问题理解

  1. 输入和输出

    • 输入是一个整数 n 和一个整数数组 sums,其中 n 是原始整数的个数,sums 包含了原始整数的所有两两和。
    • 输出是恢复出的原始整数,如果恢复失败,则输出 "Impossible"。
  2. 关键点

    • 从 sums 中得到 n(n-1)/2 个两两和,每个和是由原始整数 x_i 和 x_j 组成。
    • 我们需要恢复出这些整数,且必须保证恢复出的整数按非降序排列。

解题思路

为了恢复原始的整数,我们需要基于给定的 sums 数组推导出原始的整数集合。

1. 数学推导

  • 假设原始整数为 ( x_1, x_2, \dots, x_n )。
  • 所有两两和的形式是 ( s_{ij} = x_i + x_j ),其中 ( i \neq j )。
  • 给定的是这些和的集合 sums,大小为 ( \frac{n(n-1)}{2} )。

我们可以通过以下方式恢复原始整数:

  • 假设从 sums 中选取的最小的三项和 ( s_{12}, s_{13}, s_{23} ) 对应于 ( x_1 + x_2, x_1 + x_3, x_2 + x_3 )。

根据这三项和,我们可以通过数学推导得到原始整数 ( x_1, x_2, x_3 ):

  • ( x_1 = \frac{s_{12} + s_{13} - s_{23}}{2} )
  • ( x_2 = s_{12} - x_1 )
  • ( x_3 = s_{13} - x_1 )

这三项和可以唯一确定 ( x_1, x_2, x_3 )。之后,利用其他的两两和可以逐步推导出剩余的整数。


2. 算法步骤

  1. 排序和组合

    • sums.sort():对给定的两两和数组 sums 进行排序,方便我们从最小的和开始推导。
    • 从 sums 中选择三项最小的和 ( s_{12}, s_{13}, s_{23} ),这三项和构成了恢复整数的基础。
  2. 恢复第一个整数

    • 利用上述推导公式,从 ( s_{12}, s_{13}, s_{23} ) 中计算出 ( x_1, x_2, x_3 )。
    • 排序 ( x_1, x_2, x_3 ),确保整数的顺序是非降序的。
  3. 恢复其余的整数

    • 接下来,根据剩余的和继续恢复其他整数。对于每一个新的整数 ( x_4, x_5, \dots, x_n ),我们需要使用剩余的和中最小的项进行推导。
    • 对于每个整数 ( x_i ),我们需要找到一个和 ( x_i + x_j ) 的值,通过它来确定 ( x_j )。
  4. 验证恢复的整数

    • 恢复出所有的整数后,我们需要验证这些整数是否能够生成给定的 sums 数组。这一步是关键,它确保我们没有犯错。
    • 如果恢复出的整数集生成的所有两两和与给定的 sums 完全匹配,则返回这个解;否则返回 "Impossible"。

3. 代码实现

import itertools

def solution(n, sums):
    # 如果 sums 长度不等于 n(n-1)/2,直接返回 Impossible
    if len(sums) != n * (n - 1) // 2:
        return "Impossible"
    
    # 对 sums 进行排序,方便后续处理
    sums.sort()

    # 枚举所有可能的三项和(sums 中最小的三项和)
    for perm in itertools.combinations(sums, 3):
        # perm 为当前假设的 (x1+x2), (x1+x3), (x2+x3)
        s12, s13, s23 = perm
        # 推导出 x1
        x1 = (s12 + s13 - s23) // 2
        # 推导出 x2 和 x3
        x2 = s12 - x1
        x3 = s13 - x1
        # 将 x1, x2, x3 排序
        x = [x1, x2, x3]
        
        # 用已知的三项和推导出的 x1, x2, x3 构造所有的两两和
        all_sums = []
        for i in range(3):
            for j in range(i + 1, 3):
                all_sums.append(x[i] + x[j])
        
        # 如果当前的三项和不匹配,跳过
        if sorted(all_sums) != sorted(list(perm)):
            continue

        # 如果能通过 x1, x2, x3 恢复出原始整数
        # 使用贪心的方式恢复出所有的原始整数
        remaining_sums = sums[:]
        remaining_sums.remove(s12)
        remaining_sums.remove(s13)
        remaining_sums.remove(s23)

        for i in range(3, n):
            next_sum = remaining_sums[0]  # 当前最小的和
            x.append(next_sum - x[i-1])
            remaining_sums.remove(next_sum)
        
        # 对 x 进行排序
        x.sort()
        
        # 重新计算原始整数的所有两两和
        generated_sums = []
        for i in range(n):
            for j in range(i + 1, n):
                generated_sums.append(x[i] + x[j])

        # 如果生成的两两和与输入的 sums 相同,返回解
        if sorted(generated_sums) == sorted(sums):
            return " ".join(map(str, x))

    # 如果找不到符合条件的解,返回 Impossible
    return "Impossible"
# 测试用例
if __name__ == "__main__":
    print(solution(3, [1269, 1160, 1663]) == "383 777 886")
    print(solution(3, [1, 1, 1]) == "Impossible")
    print(solution(5, [226, 223, 225, 224, 227, 229, 228, 226, 225, 227]) == "111 112 113 114 115")
    print(solution(5, [-1, 0, -1, -2, 1, 0, -1, 1, 0, -1]) == "-1 -1 0 0 1")
    print(solution(5, [79950, 79936, 79942, 79962, 79954, 79972, 79960, 79968, 79924, 79932]) == "39953 39971 39979 39983 39989")

4. 代码详解

主要逻辑:

  1. 初始化和排序

    • 我们首先对 sums 进行排序,方便从最小的三项和开始推导。
  2. 选取三项和进行推导

    • 从 sums 中选择三个最小的两两和 ( s_{12}, s_{13}, s_{23} )。
    • 利用公式推导出 ( x_1, x_2, x_3 )。
  3. 恢复所有整数

    • 在恢复出前三个整数后,通过剩余的和继续恢复其余的整数。
    • 每恢复一个整数,我们就从剩余的和中去掉与该整数相关的两两和。
  4. 验证

    • 对恢复出的整数进行验证,确保其生成的两两和与给定的 sums 完全一致。
  5. 返回结果

    • 如果恢复出的整数符合要求,则返回这些整数;否则返回 "Impossible"。

5. 复杂度分析

时间复杂度分析

该算法的时间复杂度主要由几个部分组成。首先,我们需要对 sums 数组进行排序,sums 数组的长度为 ( \frac{n(n-1)}{2} ),排序操作的时间复杂度是 ( O(n^2 \log n) )。接下来,我们枚举所有三项和的组合,选取三项和的数量大约是 ( O(n^6) ),因为我们需要从 ( \frac{n(n-1)}{2} ) 个元素中选取三项组合。恢复剩余的整数的操作对于每一组三项和的组合是 ( O(n^2) ),因此这一部分的复杂度为 ( O(n^2) )。最后,验证恢复结果需要重新生成并排序所有两两和,时间复杂度为 ( O(n^2 \log n) )。综合这些操作,算法的总体时间复杂度是 ( O(n^6) ),其中枚举三项组合的部分是主导因素。

空间复杂度分析

空间复杂度主要来自两个方面:首先是 sums 数组,它存储了所有可能的两两和,数组的大小为 ( \frac{n(n-1)}{2} ),因此空间复杂度为 ( O(n^2) )。其次,恢复过程需要存储 ( n ) 个整数,因此额外空间为 ( O(n) )。综合来看,算法的空间复杂度为 ( O(n^2) ),主要由 sums 数组的存储占据。