问题分析
这个问题的核心目标是根据给定的两两数之和列表 sums 找出原来的 n 个整数,并按非降序返回。
对于 n 个整数,我们知道:
- 两两相加可以得到
n(n-1)/2个和(即sums列表的长度)。 - 每两个数的和在
sums中出现一次。
解决思路
-
构建两两和的关系:
-
假设原始数组是
[x1, x2, ..., xn],那么两两和是:xi+xj,1≤i<j≤nxi+xj,1≤i<j≤n
这些和会出现在
sums数组中。
-
-
从和反推原始数组:
-
由于
sums是所有数对之和的集合,最小的三个数对之和sums[0],sums[1],sums[2]由最小的三个数相加构成。假设它们是x1 + x2,x1 + x3,x2 + x3(按升序排列)。 -
我们可以通过解这个系统的方程来推测出
x1,x2, 和x3。-
设
x1 + x2 = sums[0],x1 + x3 = sums[1],x2 + x3 = sums[2]。 -
通过这三个方程可以解出
x1,x2,x3: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
-
-
-
推导其它数:
- 通过已知的
x1,x2,x3,我们可以用这三数与其他两两和去推算出其余的数。具体方法是利用已知数x1, x2, x3逐步计算出其他的整数。
- 通过已知的
-
验证:
- 根据推导出来的整数,我们重新生成所有的两两和,并与给定的
sums比对。如果一致,说明解是正确的,否则输出 "Impossible"。
- 根据推导出来的整数,我们重新生成所有的两两和,并与给定的
解题过程
-
预处理:
- 排序
sums列表,假设排序后,前三个数就是x1 + x2, x1 + x3, x2 + x3。
- 排序
-
解方程:
- 通过前面的方法解出
x1,x2,x3。
- 通过前面的方法解出
-
推算剩余数:
- 用这些数去推算出其它的数。
-
检查答案的有效性:
- 生成所有可能的两两和,并与给定的
sums进行比较。
- 生成所有可能的两两和,并与给定的
-
返回结果:
- 如果成功找到解,输出结果;如果没有解,输出 "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"
代码解释
-
排序和提取前三个和:
- 排序后,
sums[0],sums[1],sums[2]分别是x1+x2,x1+x3,x2+x3。
- 排序后,
-
解方程:
- 使用数学公式解出
x1,x2,x3。
- 使用数学公式解出
-
推算其他数:
- 通过剩余的和,逐步推算出原始数组中的其他数字。
-
验证:
- 使用
Counter来确保每一个两两和都能在sums中找到匹配,确保没有遗漏或者重复。
- 使用
-
返回结果:
- 如果验证通过,则输出原始数组,否则返回 "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包含了所有的两两和。
- 需要一个
总结
该问题的核心是利用给定的两两和通过方程推算出原始数组中的整数,并验证其有效性。通过数学推导和双指针/计数器技术,可以有效地解决此问题。