Topics › Recursion and Backtracking › Subsequences Pattern
Subsets I
Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given an array nums of n integers. Return the sum of every subset of nums: one number per subset, so the answer has exactly 2^n entries, including 0 for the empty subset. Equal sums coming from different subsets are all kept. The sums may be returned in any order; the methods below return them in non-decreasing order so the output is deterministic and easy to compare.
This is the problem that the A2Z sheet lists as “Subset Sum-I” (also known as “Subset Sums” on other sheets). It is the simplest member of the subsequence family: no target to hit, no duplicates to remove, just visit every subset once and report its sum. The last section shows how the same recursion produces the subsets themselves, which is what some readers expect from the title.
Constraints: 0 ≤ n ≤ 15, -10^4 ≤ nums[i] ≤ 10^4, so every sum fits comfortably in an int.
Example 1
nums = [2, 3]
answer = [0, 2, 3, 5] // {} , {2}, {3}, {2, 3}
Example 2
nums = [5, 2, 1]
answer = [0, 1, 2, 3, 5, 6, 7, 8]
// {} {1} {2} {1,2} {5} {5,1} {5,2} {5,2,1}
Example 3
nums = [3, 1, 2]
answer = [0, 1, 2, 3, 3, 4, 5, 6] // 3 appears twice: from {3} and from {1, 2}
Approach 1: Brute force, build each subset from a bitmask
Intuition. A subset of n elements is an n-bit choice: bit i set means nums[i] is included. Counting from 0 to 2^n - 1 visits every subset exactly once, and for each one we add up the elements whose bit is set. The approach is simple and iterative, and for n ≤ 15 it is perfectly fast; its only waste is recomputing each sum from scratch, n operations per subset, even though the subset for mask and for mask + 1 share most of their elements.
Algorithm.
- For every
maskfrom0to2^n - 1: - Sum
nums[i]over the set bitsiofmaskand append the sum. - Sort the list of sums and return it.
static List<Integer> bruteForce(int[] nums) {
int n = nums.length;
List<Integer> sums = new ArrayList<>();
for (int mask = 0; mask < (1 << n); mask++) {
int sum = 0;
for (int i = 0; i < n; i++) {
if ((mask & (1 << i)) != 0) sum += nums[i];
}
sums.add(sum);
}
Collections.sort(sums);
return sums;
}
Complexity. Time O(2^n · n) to compute the sums plus O(2^n · n) for sorting 2^n numbers (log 2^n = n). Space O(2^n) for the answer.
Approach 2: Optimal, pick / not-pick recursion with a running sum
Intuition. Instead of deciding all n bits and then summing, decide them one at a time and keep the partial sum as we go. At index i with running sum sum, the subsets of nums[i..] split into those that contain nums[i] (continue with sum + nums[i]) and those that do not (continue with sum). When the index reaches n, every element has been decided and sum is the sum of exactly one subset, so we record it. Each leaf now costs O(1) rather than O(n), and the recursion tree has 2^n leaves and 2^n - 1 internal nodes, so the total work is linear in the output size.
This is the standard subsequence recursion; it generates the subsets in a fixed order (the pick branch first), which is why the raw output is not sorted and a final sort is added for a canonical answer.
Algorithm.
sumRec(i, sum): ifi == n, appendsumand return.- Recurse with
(i + 1, sum + nums[i])(pick). - Recurse with
(i + 1, sum)(not pick). - Start with
sumRec(0, 0), then sort.
static List<Integer> optimal(int[] nums) {
List<Integer> sums = new ArrayList<>();
sumRec(nums, 0, 0, sums);
Collections.sort(sums);
return sums;
}
static void sumRec(int[] nums, int i, int sum, List<Integer> sums) {
if (i == nums.length) {
sums.add(sum);
return;
}
sumRec(nums, i + 1, sum + nums[i], sums); // pick nums[i]
sumRec(nums, i + 1, sum, sums); // leave nums[i] out
}
Dry run on nums = [2, 3]:
| call (i, sum) | branch | result |
|---|---|---|
| (0, 0) | pick 2 → (1, 2) | |
| (1, 2) | pick 3 → (2, 5) | leaf: record 5 |
| (1, 2) | not pick → (2, 2) | leaf: record 2 |
| (0, 0) | not pick → (1, 0) | |
| (1, 0) | pick 3 → (2, 3) | leaf: record 3 |
| (1, 0) | not pick → (2, 0) | leaf: record 0 |
Raw order [5, 2, 3, 0], sorted [0, 2, 3, 5].
Complexity. Time O(2^n) for the recursion plus O(2^n · n) if the final sort is required; without the sort it is strictly O(2^n). Space O(n) for the recursion stack, in addition to the O(2^n) output.
Approach 3: Iterative doubling
Intuition. The subsets of the first i + 1 elements are exactly the subsets of the first i elements, each either left alone or extended by nums[i]. So if S_i is the list of sums for the first i elements, then S_{i+1} is S_i followed by every value of S_i plus nums[i]. Starting from S_0 = [0] and applying this n times builds all 2^n sums with no recursion at all, and each new sum costs one addition. It is the same idea as the pick / not-pick tree, read level by level instead of depth first.
Algorithm.
- Start with
sums = [0]. - For each
xinnums: letsizebe the current length; forjin0 .. size - 1, appendsums[j] + x. - Sort and return.
static List<Integer> iterative(int[] nums) {
List<Integer> sums = new ArrayList<>();
sums.add(0); // the empty subset
for (int x : nums) {
int size = sums.size();
for (int j = 0; j < size; j++) sums.add(sums.get(j) + x);
}
Collections.sort(sums);
return sums;
}
Complexity. Time O(2^n) for the doubling (1 + 2 + 4 + ... + 2^(n-1) additions) plus the optional sort. Space O(2^n) for the output and nothing else; there is no recursion stack.
Capturing size before the inner loop matters: the list grows while we iterate, and reading sums.size() in the loop condition would never terminate.
If you need the subsets themselves
The sum is just one way to summarise a subset. The identical recursion, with a working list instead of a running sum, produces the subsets as lists; this is the Power Set problem, included here because the title of this entry is often read that way. Copy the working list at the leaf; storing cur itself would store a reference that later backtracking empties.
static List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> out = new ArrayList<>();
subsetRec(nums, 0, new ArrayList<>(), out);
return out;
}
static void subsetRec(int[] nums, int i, List<Integer> cur, List<List<Integer>> out) {
if (i == nums.length) {
out.add(new ArrayList<>(cur));
return;
}
cur.add(nums[i]); // pick nums[i]
subsetRec(nums, i + 1, cur, out);
cur.remove(cur.size() - 1); // undo, then leave nums[i] out
subsetRec(nums, i + 1, cur, out);
}
Time O(2^n · n) because each of the 2^n leaves copies a list of up to n elements. The test harness sums every generated subset and checks that the sorted result equals the three sum methods, so the four pieces of code are verified against each other.
Edge cases and pitfalls
- Empty array. The only subset is the empty one, so the answer is
[0]. All methods handle it: the mask loop runs once, the recursion hits its base case immediately, and the doubling never runs its loop. - Duplicate sums.
[1, 1]gives[0, 1, 1, 2]; the two1s are different subsets and both are reported. Do not de-duplicate; that is a different problem. - Negative numbers. They need no special handling;
[-1, 2]gives[-1, 0, 1, 2]. Sorting is what places the negative sum first. - Overflow. With
|nums[i]| ≤ 10^4andn ≤ 15the largest sum is1.5 × 10^5, far insideint. If the constraints were larger (values near10^9), switch the running sum tolong. - Output size.
2^nentries is the answer’s inherent size; forn = 15that is 32 768 integers, which is why the constraint is small. No algorithm can beatO(2^n)here. - Growing a list while iterating it. In the doubling approach, read the size once before the inner loop.
Related problems
- Power Set is the subsets-as-lists version shown in the last section.
- Subsets II asks for unique subsets when the input contains duplicates.
- Count all subsequences with sum K counts how many of these sums equal a target.
- Check if there exists a subsequence with sum K asks whether any of them equals a target, with early exit.
- Power Set Bit Manipulation is the bitmask enumeration of Approach 1 studied on its own.
- Partition a set into two subsets with minimum absolute sum difference is what subset sums are used for once
ngrows and dynamic programming replaces enumeration.