Subsets

Question (LC.78)

Given a set of distinct integers, nums, return all possible subsets.

The solution set must not contain duplicate subsets. Assume input does not contain duplicates.

Example

Input: nums = [1, 2, 3]
Return: 
[ [3], [1], [2],
  [1,2,3],
  [1,3], [2,3], [1,2],
  []
]

Analysis

Finding all possible subsets - we need DFS for sure.

Code

Main function - pass in a reference to DFS

public List<List<Integer>> subsets(int[] nums) {
    List<List<Integer>> results = new ArrayList<>();
    if (nums == null || nums.length == 0) return results;
    subsetDFS(results, nums, 0, new ArrayList<Integer>());
    return results;
}

DFS Tree

private void subsetDFS(List<List<Integer>> results, 
    int[] nums, int startIndex, List<Integer> subset) {
    results.add(new ArrayList<>(subset));
    for (int i = startIndex; i < nums.length; i++) {
        subset.add(nums[i]);
        subsetDFS(results, nums, i + 1, subset);
        subset.remove(subset.size() - 1);
    }
}

This is an introduction to recursion tree and DFS tree. So we want to play around and analyze it for a bit.

What happens if we do this?

results.add(new ArrayList<>(subset));
if (startIndex == nums.length) {
     return;
}
for (int i = startIndex; i < nums.length; i++) {
    subset.add(nums[i]);
    subsetDFS(results, nums, i + 1, subset);
    subset.remove(subset.size() - 1);
}

Emmm nothing has changed because the for loop forces the DFS stops when startIndex == nums.length.
How about if we switch them?

if (startIndex == nums.length) {
     return;
}
results.add(new ArrayList<>(subset));
for (int i = startIndex; i < nums.length; i++) {
    subset.add(nums[i]);
    subsetDFS(results, nums, i + 1, subset);
    subset.remove(subset.size() - 1);
}

This will only output the last level of the recursion tree.
Now, how do we only display the second level [1,3], [2,3], [1,2]?

if (startIndex == nums.length - 1) {
    results.add(new ArrayList<>(subset));
    return;
}
for (int i = startIndex; i < nums.length; i++) {
    subset.add(nums[i]);
    subsetDFS(results, nums, i + 1, subset);
    subset.remove(subset.size() - 1);
}

ᶘ ͡°ᴥ͡°ᶅ why doesn't this work??
Well, it was trick question. [1,3], [2,3], [1,2] is when the size of the subset is 2 but is not really the second level of the recursion tree which is [1,2], [2]. So the correct way to do it is

if (subset.size() == 2) {
    results.add(new ArrayList<>(subset));
    return;
}
for (int i = startIndex; i < nums.length; i++) {
    subset.add(nums[i]);
    subsetDFS(results, nums, i + 1, subset);
    subset.remove(subset.size() - 1);
}

Time Complexity

#subproblems * time/subproblem = 2^n * O(1) = O(2^n)

To Do

first recurve vs for loop
array switch approach
why the first way is less efficient
iterative approach


Follow Up (LC.90)

What if the input has duplicates?

Example

Input: nums = [1, 2, 2]
Output: 
[
[], 
[1], [2],
[1, 2], [2, 2],
[1, 2, 2]
]

Analysis

The easiest and space-costly way would be having a hashset to store all the subsets that are visited. But can we prune some repetitive subtrees?

Code

for (int i = startIndex; i < nums.length; i++) {
    if (i > startIndex && nums[i] == nums[i-1]) {
        continue;
    }
    subset.add(nums[i]);
    subsetDFS(results, nums, i + 1, subset);
    subset.remove(subset.size() - 1);
}

So the key is i > startIndex && nums[i] == nums[i-1]. Since we already sorted nums in this case, we can skip duplicates by checking current_element == previous_element. Also i > startIndex guarantees that this is not the first iteration in the current loop.

results matching ""

    No results matching ""