Problem

You are given an integer n representing the length of an unknown array that you are trying to recover. You are also given an array sums containing the values of all 2ⁿ subset sums of the unknown array (in no particular order).

Return the array ans of length n representing the unknown array. If multiple answers exist, return any of them.

An array sub is a subset of an array arr if sub can be obtained from arr by deleting some (possibly zero or all) elements of arr. The sum of the elements in sub is one possible subset sum of arr. The sum of an empty array is considered to be 0.

Note: Test cases are generated such that there will always be at least one correct answer.

https://leetcode.com/problems/find-array-given-subset-sums/

Example 1:

Input: n = 3, sums = [-3,-2,-1,0,0,1,2,3]
Output: [1,2,-3]
Explanation: [1,2,-3] is able to achieve the given subset sums:

  • []: sum is 0
  • [1]: sum is 1
  • [2]: sum is 2
  • [1,2]: sum is 3
  • [-3]: sum is -3
  • [1,-3]: sum is -2
  • [2,-3]: sum is -1
  • [1,2,-3]: sum is 0

Note that any permutation of [1,2,-3] and also any permutation of [-1,-2,3] will also be accepted.

Example 2:

Input: n = 2, sums = [0,0,0,0]
Output: [0,0]
Explanation: The only correct answer is [0,0].

Example 3:

Input: n = 4, sums = [0,0,5,5,4,-1,4,9,9,-1,4,3,4,8,3,8]
Output: [0,-1,4,5]
Explanation: [0,-1,4,5] is able to achieve the given subset sums.

Constraints:

  • 1 <= n <= 15
  • sums.length == 2ⁿ
  • -10⁴ <= sums[i] <= 10⁴

Test Cases

1
2
class Solution:
def recoverArray(self, n: int, sums: List[int]) -> List[int]:
solution_test.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import itertools
import pytest

from solution import Solution


@pytest.mark.parametrize('n, sums, expected', [
(3, [-3,-2,-1,0,0,1,2,3], [1,2,-3]),
(2, [0,0,0,0], [0,0]),
(4, [0,0,5,5,4,-1,4,9,9,-1,4,3,4,8,3,8], [0,-1,4,5]),

(4, [0, -5, -2, 6, 10, -7, 1, 5, 4, 8, 16, -1, 3, 11, 14, 9], [-5, -2, 6, 10]),
(
7,
[1588,2762,1459,308,969,1211,775,1987,1756,1733,957,1485,1277,1895,2260,2078,1041,1770,1551,1713,1277,867,1713,1859,2580,1721,1223,1770,467,1938,1072,810,1436,787,1369,539,1793,1998,1485,787,2041,721,490,1188,1095,1211,1539,1049,2169,582,1531,1266,1084,285,1574,1231,-182,824,2260,1357,1006,2157,2944,2295,1303,1266,2659,764,0,182,593,1667,1174,1326,721,1816,2762,1496,103,2454,1678,2223,320,1998,1223,684,605,1393,490,946,1508,672,775,1987,1006,764,2180,285,1448,1756,992,992,1975,1975,1254,1496,1508,1049,0,1405,2090,1314,502,1952,2477,2442,2272,1029,2041,2272,2477,1690,502,1805,1551,903,1254,1539],
[-182, 182, 285, 490, 502, 721, 764]
),
(
9,
[190,877,-182,-910,318,-653,-1742,921,-605,-989,-2229,-90,-408,149,-1294,0,692,663,-948,-297,-1101,475,494,622,-795,-288,-490,457,-1005,-515,144,565,202,1429,136,-716,-919,902,308,-531,-150,-659,-592,145,20,82,145,785,298,-1422,-1400,-486,-376,-989,-1218,-455,-147,-338,183,-181,661,-445,-640,-284,-103,968,1121,107,1455,-487,273,2513,1397,952,129,-366,-809,184,939,340,-489,152,-821,648,171,-1492,-163,647,428,-637,-807,581,-160,1118,1414,615,-144,-901,2221,635,498,597,507,-964,1376,-474,292,-1021,-185,-1409,-1297,-179,1468,1398,-650,924,-993,157,1747,599,-2047,-306,1776,-787,-135,-684,-685,-931,384,-618,-147,-335,-1284,585,-935,603,-1281,997,1137,657,-534,-207,647,-976,-198,-2092,142,-131,-963,-614,-1418,-194,-627,136,158,-1450,-423,22,-944,-178,-182,-239,-166,1760,-623,432,-106,-675,-316,-24,523,1684,895,-639,1286,-621,1715,-655,-160,1900,-1908,-176,-945,1410,-153,-1142,66,-1129,282,1260,936,628,1071,1706,-44,1913,311,-697,952,-1434,619,603,-656,-624,-646,1394,-684,491,1102,8,-1771,931,-1329,-1800,-322,218,-700,1458,1385,943,510,326,1261,1150,1702,476,-1739,590,-192,-119,-226,-589,953,264,-210,-1310,-971,1442,123,965,352,594,-10,-32,940,984,450,355,113,95,-173,-1002,-932,148,-942,-634,1592,330,-977,556,-643,-168,-1171,114,911,189,-102,631,-547,949,-481,202,-1145,-1602,-313,-169,-46,920,1884,626,120,154,644,673,783,616,-363,1439,-668,1216,829,-367,-671,-1079,1013,1228,1270,1077,908,139,-169,815,431,1718,215,-2216,-1095,1750,1410,-1434,441,1147,-1774,-926,161,-411,-611,295,-1482,-593,-1453,-992,463,-979,763,-658,-300,123,-605,-1421,248,-1416,676,600,-1113,-379,632,634,581,-1008,-1161,206,-960,110,-137,1423,2068,34,-729,2192,-124,-1126,387,1731,-1755,-853,-58,955,-172,276,797,-226,-1108,170,-477,-1466,-967,978,918,1273,2023,-465,-1479,-242,479,-1438,277,140,-1924,-1174,1392,-913,2205,749,629,-1616,155,-627,-503,421,-499,-580,-115,-1117,-518,173,91,79,-1037,-955,1257,339,-214,78,289,-1263,965,260,886,-652,100,126,1084,1578,-319,-1387,342,-973,-2537,568,-1784,-452,907,771,174,186,1194,462,-773,-148,-663,-1130,-156,1363,-272,-1708,-1730,177,969,-1447,-468,-976,1055,-839,466,-201,-14,660,705,587,399,-197,-230,613,1106,-609,453,-208,-456,873,1089,-1463,158,-331,-354,-897,321,-1937,-1252,1305,-1726,124,610,947,-500,-301,-364,-681,981,314,168,660,1426,1239,343,-213,889,651,-1240,1105,-522,569,639,444,132,-138,-671,1093,465,-1285,-342,-134,307,-672,-195,-350,-687,111,-332,-2245,-164,-345],
[-807, -795, -490, -445, 292, 308, 321, 763, 829]
),
])
class Test:
def test_solution(self, n, sums, expected):
sol = Solution()
result = sol.recoverArray(n, sums.copy())
assert sorted(result) == sorted(expected) or self._sum_all(result) == sorted(sums)

def _sum_all(self, arr: list[int]) -> list[int]:
sums = []
for r in range(len(arr) + 1):
for vals in itertools.combinations(arr, r):
sums.append(sum(vals))

return sorted(sums)

Thoughts

被正负数混合的情况搞到头疼。最后发现一直徘徊在正确解法的门口却始终没能踏入第一步。

如果 arr 中有正数,显然 sums 中最大值就是所有正数之和(记为 s1)。它与第二大的值(记为 s2)之差(d = s1 - s2 >= 0)有三种可能,一是 d = 0,说明 arr 中有 0;二是 d 是最小的正数;三是 -d 是最大的负数。

实际上如果 arr 中没有正数也类似,那么 s1 = 0,要么 d = 0 存在于 arr 中,要么 -d 是最大的负数(少了 d 是正数的可能)。

先看 d 存在于 arr(d 是最小的正数)的情况。这时候希望能够把 sums 分成两半,一半由所有包含 d 在内的 subset 之和构成,另一半由所有不包含 d 的 subset 之和构成。取所有不包含 d 的 subset 之和(一共有 len(sums) / 2 个),就是可以按照相同逻辑递归处理的子问题。

关键在于如何拆分 sums

开始的想法是先计算 arr 的总和(arr_sum = sum(sums) / 2ⁿ⁻¹),然后对于 sums 中的任何一个值 a,一定有 b = arr_sum - a 也在 sums 中,且 a 和 b 产生于互补的两个 subset。显然 d 属于且只属于其中的一个。看 a - db - d 哪一个不存在于 sums,对应的就是不含 d 的 subset 之和。

麻烦就在于,有可能 a - db - d 都存在于 sums,如果 a ≠ b 那就不能随便选。

还有个想法是看 sums 中任何一个值 a,如果 a 是某个包含 d 的 subset 之和,那么必然有 a - dnums 中;如果 a - d 不存在,则 a 一定是某个不含 d 的 subset 之和。遇到同样的问题,即使 a 是某个不含 d 的 subset 之和,a - d 也可能存在于 sums 中。

做了 954. Array of Doubled Pairs 之后发现二者要处理的问题是类似的。在 problem 954 中需要判断 arr 中任意一个值 valval * 2val / 2 是否也在 arr 中,而二者可能同时存在,无法确定应该跟谁配对。解决的办法是(考虑到正负数则按绝对值)排序,按顺序检查,val 的两倍一定排在 val 之后,避免了歧义性。

借鉴这个想法,对 sums 按照降序排列,因为 d >= 0,显然对于任意的一对 aa - da - d 一定排在后边。对于第一个数 a,可知 a - d 一定存在且排在后边,把 a - d 删掉,就能保证之后遇到的每个值 a,对应的 a - d 一定还存在且排在 a 后边。这样就可以选出来所有不含 d 的 subset 之和。

前边假设了 d 存在于 arr 中,这个假设不一定成立。如果 arr 中没有 d,而是有 -d,那么对于刚才遍历到的任意一对 aa - d,都可以看作是 b - (-d)b(其中 b = a + (-d)),仍然可以选出来所有不含 -d 的 subset 之和。

所以先不管实际存在于 arr 中的到底是 d 还是 -d,直接按降序遍历 sums,把它拆分成两个一样大的数组。任意一对 aa - d,把前者放入子数组 A,后者放入子数组 B。最后看 dA 中是否存在。如果 dA 中存在,说明正数 d 存在于 arr 中,且 B 里的值都是不含 d 的 subset 之和。否则说明负数 -d 存在于 arr 中,且 A 里的值都是不含 -d 的 subset 之和。

当然也有可能 d-d 同时存在于 arr 中,没关系,这一轮先拿到 d,下一轮就能识别到 -d(如果没有更多的 d)。

回过头再看,其实也可以先取 sums 中最小的两个值(s1 - s2 = d <= 0,可能是负数 d 也可能是正数 -d),然后按照升序遍历 nums,效果是完全一样的。

外层循环共 n 次,每次都要遍历一遍剩余的 sums 数组,并确定出 arr 中的一个值。总共时间复杂度 O(n * 2ⁿ),空间复杂度 O(2ⁿ)

Code

solution.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from collections import Counter


class Solution:
def recoverArray(self, n: int, sums: list[int]) -> list[int]:
arr = [None] * n
sums.sort() # It doesn't matter whether `reverse` is True or False.
m = len(sums)

for i in range(n):
d = sums[0] - sums[1] # d or -d must in arr.
m >>= 1
sums1 = [0] * m
sums2 = [0] * m
counts = Counter(sums)
j = 0
for a in sums:
if counts[a] == 0: continue
b = a - d
counts[a] -= 1
counts[b] -= 1 # b must exist.
sums1[j] = a
sums2[j] = b
j += 1

# sums1 and sums2 are both sorted.
if d in sums1:
arr[i] = d
sums = sums2
else:
arr[i] = -d
sums = sums1

return arr