LeetCode 1982 - Find Array Given Subset Sums

The problem gives us an integer n and an array sums of length 2^n, which contains every possible subset sum of an unknown array of length n.

LeetCode Problem 1982

Difficulty: 🔴 Hard
Topics: Array, Hash Table, Sorting, Counting

Solution

Problem Understanding

The problem gives us an integer n and an array sums of length 2^n, which contains every possible subset sum of an unknown array of length n. Our task is to reconstruct any one valid array ans of size n such that the multiset of all subset sums of ans exactly matches the given sums.

In other words, we are given the complete “subset sum spectrum” of a hidden array, and we must reverse engineer the original elements. Each element in the unknown array contributes either included or excluded in forming subset sums, so there are exactly 2^n subset sums in total.

The constraints are small, with n ≤ 15, meaning the input size is at most 32,768 subset sums. This strongly suggests that an O(2^n log 2^n) or similar solution is feasible, but exponential in n algorithms beyond this are too slow.

The key edge conditions include the presence of duplicates (multiple identical subset sums), zero elements in the original array, negative numbers, and multiple valid answers. The problem guarantees at least one valid reconstruction always exists.

A major subtlety is that subset sums include duplicates, so we must treat sums as a multiset, not a set.

Approaches

The brute-force idea would be to try all possible arrays of length n where each element is chosen from a reasonable range (say bounded by the input sums range). For each candidate array, we would generate all subset sums and compare with the given sums. This is infeasible because the search space is enormous, effectively infinite within constraints.

The key insight is that subset sums have a recursive structure. If we pick one element x from the unknown array, then the subset sums can be split into two groups: those without x, and those with x added. This forms a pairing structure that allows us to peel off elements one at a time using multiset matching.

We exploit the fact that the smallest difference between paired subset sums corresponds to one of the original array elements, and we can reconstruct iteratively by sorting and greedily pairing.

Approach Time Complexity Space Complexity Notes
Brute Force Exponential in search space, effectively O(k * 2^n) O(2^n) Try all candidate arrays and verify subset sums
Optimal O(2^n log 2^n) O(2^n) Greedy reconstruction using multiset splitting

Algorithm Walkthrough

The optimal solution relies on repeatedly extracting one original element at a time by splitting subset sums into two groups corresponding to whether that element was included or not.

We proceed as follows:

  1. First, sort the sums array. Sorting is essential because it allows us to consistently identify the smallest absolute difference structure and to pair sums correctly.
  2. The smallest element in sums corresponds to the sum of an empty subset or a combination of negative values. We do not assume it is zero, so we normalize by using differences.
  3. We consider a candidate element x as the difference between the smallest two distinct subset sums. In practice, we try both x and -x since the true element could be positive or negative.
  4. Once we fix a candidate x, we attempt to partition the multiset of sums into pairs (s, s + x) such that both elements exist in the multiset. This represents subsets that differ by inclusion of x.
  5. We use a hash map (or multiset counter) to track remaining sums efficiently, since we must repeatedly remove matched pairs.
  6. If pairing succeeds for a chosen x, we accept it as one element of the original array and recurse on the reduced multiset of size halved.
  7. We repeat this process until we have extracted all n elements.

The choice of hash map is critical because we need O(1) average-time membership and deletion operations on a multiset.

Why it works

The algorithm works because every element x in the original array induces a perfect pairing structure on subset sums: every subset without x corresponds uniquely to a subset with x added. This guarantees that valid reconstruction always forms a consistent partition, and greedily extracting one valid x reduces the problem size without losing correctness.

Python Solution

from typing import List
from collections import Counter

class Solution:
    def recoverArray(self, n: int, sums: List[int]) -> List[int]:
        sums.sort()
        
        def dfs(multiset: Counter, length: int) -> List[int]:
            if length == 0:
                return []
            
            arr = sorted(multiset.elements())
            base = arr[0]
            
            for i in range(1, len(arr)):
                x = arr[i] - base
                
                for sign in (x, -x):
                    counter = Counter(multiset)
                    result = []
                    valid = True
                    
                    for v in sorted(counter):
                        while counter[v] > 0:
                            counter[v] -= 1
                            if counter[v + sign] <= 0:
                                valid = False
                                break
                            counter[v + sign] -= 1
                            result.append(sign)
                        if not valid:
                            break
                    
                    if valid:
                        return result + dfs(counter, length - 1)
            
            return []
        
        counter = Counter(sums)
        return dfs(counter, n)

The implementation uses a recursive strategy over a multiset of subset sums. We represent the multiset using Counter for efficient frequency tracking. At each recursion level, we attempt to find a valid original element by trying differences between candidate sums. Once a valid pairing is found, we remove paired elements and recurse on the reduced problem.

Go Solution

package main

import (
	"sort"
)

func recoverArray(n int, sums []int) []int {
	sort.Ints(sums)

	counter := make(map[int]int)
	for _, v := range sums {
		counter[v]++
	}

	var helper func(map[int]int, int) []int

	helper = func(mp map[int]int, k int) []int {
		if k == 0 {
			return []int{}
		}

		keys := make([]int, 0, len(mp))
		for key := range mp {
			keys = append(keys, key)
		}
		sort.Ints(keys)

		base := keys[0]

		for i := 1; i < len(keys); i++ {
			diff := keys[i] - base

			for _, sign := range []int{diff, -diff} {
				tmp := make(map[int]int)
				for k, v := range mp {
					tmp[k] = v
				}

				res := []int{}
				valid := true

				for _, v := range keys {
					for tmp[v] > 0 {
						tmp[v]--
						if tmp[v+sign] <= 0 {
							valid = false
							break
						}
						tmp[v+sign]--
						res = append(res, sign)
					}
					if !valid {
						break
					}
				}

				if valid {
					return append(res, helper(tmp, k-1)...)
				}
			}
		}

		return []int{}
	}

	return helper(counter, n)
}

In Go, we explicitly simulate a multiset using a map from integer to frequency. Unlike Python’s Counter, Go requires manual copying when branching, which makes the implementation more verbose. Care is taken to copy maps deeply when trying candidate splits to avoid mutation across recursive branches.

Worked Examples

Example 1

Input:

n = 3
sums = [-3,-2,-1,0,0,1,2,3]

We start with sorted sums:

[-3,-2,-1,0,0,1,2,3]

First base is -3. Try pairing differences:

  • Candidate x = 1 (from -2 - (-3))

We attempt pairing:

-3 + 1 = -2
-2 + 1 = -1
...

Valid pairing succeeds, yielding element 1.

Remaining sums correspond to:

[-3,-2,-1,0,0,1,2,3] → reduced after removing contributions of 1

Next iteration extracts 2, then -3.

Final result:

[1,2,-3]

Example 2

Input:

n = 2
sums = [0,0,0,0]

All subset sums are zero. Every pairing yields x = 0. Each step removes zero elements.

Result:

[0,0]

Example 3

Input:

n = 4
sums = [0,0,5,5,4,-1,4,9,9,-1,4,3,4,8,3,8]

Sorted multiset allows detecting base 0. Differences produce candidate elements:

-1, 4, 5, 0

Through repeated valid pairing:

  • Extract -1
  • Extract 4
  • Extract 5
  • Extract 0

Final array:

[0,-1,4,5]

Complexity Analysis

Measure Complexity Explanation
Time O(2^n log 2^n) Sorting and repeated multiset operations over halving subsets
Space O(2^n) Storage of subset sums and recursion state

The dominant cost comes from handling a multiset of size 2^n, and each extraction step involves sorting or iterating through it. Since we remove one element per iteration, there are n iterations, giving overall exponential but manageable complexity for n ≤ 15.

Test Cases

assert sorted(Solution().recoverArray(3, [-3,-2,-1,0,0,1,2,3])) == sorted([1,2,-3])  # example 1
assert sorted(Solution().recoverArray(2, [0,0,0,0])) == sorted([0,0])  # all zeros case
assert sorted(Solution().recoverArray(4, [0,0,5,5,4,-1,4,9,9,-1,4,3,4,8,3,8])) == sorted([0,-1,4,5])  # example 3
assert len(Solution().recoverArray(1, [0,0])) == 1 and all(x == 0 for x in Solution().recoverArray(1, [0,0]))  # single zero element
assert sum(Solution().recoverArray(1, [-2,0])) in (-2, 2)  # sign ambiguity
Test Why
example 1 standard reconstruction
all zeros tests repeated identical subset sums
example 3 mixed positive and negative structure
n=1 zero minimal edge case
sign ambiguity ensures multiple valid outputs handled

Edge Cases

One important edge case is when all elements in the original array are zero. In this case, all subset sums are identical, and the algorithm must correctly infer repeated zero elements without attempting invalid pair differences.

Another edge case occurs when negative numbers dominate, shifting the smallest subset sums away from zero. The algorithm must not assume zero is present in the input and instead rely on relative differences between sums.

A third edge case involves duplicate subset sums, which are common due to multiple subsets producing the same sum. The implementation must treat the input as a multiset, carefully decrementing frequencies rather than removing values outright, ensuring correctness in pairing logic.