问题描述
n 个整数两两相加可以得到 n(n - 1) / 2 个和。我们的目标是:根据这些和找出原来的 n 个整数。
按非降序排序返回这 n 个数,如果无解,输出 "Impossible"。
问题分析
这个问题要求我们从给定的 n 个整数的和 sums 中恢复出原始的 n 个整数。已知的是,sums 数组包含了原始整数的所有可能的两两和,数组的大小为 n(n-1)/2,并且这些和是两两相加得到的。我们的目标是根据这些和找出原来的整数,并按非降序返回。如果无法恢复出原始整数,则返回 "Impossible"。
问题理解
-
输入和输出:
- 输入是一个整数
n和一个整数数组sums,其中n是原始整数的个数,sums包含了原始整数的所有两两和。 - 输出是恢复出的原始整数,如果恢复失败,则输出 "Impossible"。
- 输入是一个整数
-
关键点:
- 从
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. 算法步骤
-
排序和组合:
sums.sort():对给定的两两和数组sums进行排序,方便我们从最小的和开始推导。- 从
sums中选择三项最小的和 ( s_{12}, s_{13}, s_{23} ),这三项和构成了恢复整数的基础。
-
恢复第一个整数:
- 利用上述推导公式,从 ( s_{12}, s_{13}, s_{23} ) 中计算出 ( x_1, x_2, x_3 )。
- 排序 ( x_1, x_2, x_3 ),确保整数的顺序是非降序的。
-
恢复其余的整数:
- 接下来,根据剩余的和继续恢复其他整数。对于每一个新的整数 ( x_4, x_5, \dots, x_n ),我们需要使用剩余的和中最小的项进行推导。
- 对于每个整数 ( x_i ),我们需要找到一个和 ( x_i + x_j ) 的值,通过它来确定 ( x_j )。
-
验证恢复的整数:
- 恢复出所有的整数后,我们需要验证这些整数是否能够生成给定的
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. 代码详解
主要逻辑:
-
初始化和排序:
- 我们首先对
sums进行排序,方便从最小的三项和开始推导。
- 我们首先对
-
选取三项和进行推导:
- 从
sums中选择三个最小的两两和 ( s_{12}, s_{13}, s_{23} )。 - 利用公式推导出 ( x_1, x_2, x_3 )。
- 从
-
恢复所有整数:
- 在恢复出前三个整数后,通过剩余的和继续恢复其余的整数。
- 每恢复一个整数,我们就从剩余的和中去掉与该整数相关的两两和。
-
验证:
- 对恢复出的整数进行验证,确保其生成的两两和与给定的
sums完全一致。
- 对恢复出的整数进行验证,确保其生成的两两和与给定的
-
返回结果:
- 如果恢复出的整数符合要求,则返回这些整数;否则返回 "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 数组的存储占据。