Given an integer array nums
of unique elements, return all possible subsets (the power set).
The solution set must not contain duplicate subsets. Return the solution in any order.
Example 1:
Input: nums = [1,2,3] Output: [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
Example 2:
Input: nums = [0] Output: [[],[0]]
Constraints:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
nums
are unique.This problem asks to find all possible subsets (power set) of a given integer array nums
containing unique elements. The solution must not contain duplicate subsets and can be returned in any order.
This approach uses recursion to explore all possible combinations. The core idea is to iterate through the input array. For each element, we have two choices: either include it in the current subset or exclude it. This decision branching is handled recursively.
Algorithm:
Base Case: If we've reached the end of the input array (i == nums.length
), we've formed a complete subset. Add a copy of the current subset (t
) to the result (ans
).
Recursive Steps:
dfs(i + 1)
to explore subsets without the current element.nums[i]
) to the current subset (t
), then recursively call dfs(i + 1)
to explore subsets including the current element. After the recursive call, remove the element (nums[i]
) from t
(backtracking) to explore other possibilities.Initialization: Start the recursion with dfs(0)
.
Time Complexity: O(N * 2N). There are 2N possible subsets, and constructing each subset takes O(N) time in the worst case (when the subset contains all elements).
Space Complexity: O(N). The space is primarily used by the recursion stack, which has a depth of at most N.
Code (Python3):
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
ans = []
t = []
def dfs(i: int):
if i == len(nums):
ans.append(t.copy()) #Important: Append a copy, not t itself.
return
dfs(i + 1) # Exclude
t.append(nums[i])
dfs(i + 1) # Include
t.pop()
dfs(0)
return ans
Code (Java):
class Solution {
List<List<Integer>> ans = new ArrayList<>();
List<Integer> t = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
dfs(nums, 0);
return ans;
}
private void dfs(int[] nums, int i) {
if (i == nums.length) {
ans.add(new ArrayList<>(t)); //Important: Create a copy.
return;
}
dfs(nums, i + 1); // Exclude
t.add(nums[i]);
dfs(nums, i + 1); // Include
t.remove(t.size() - 1);
}
}
Similar implementations can be written in other languages (C++, Go, Javascript, etc.) following the same logic.
This approach leverages the fact that each subset can be represented by a unique binary number of length N (where N is the number of elements in nums
). Each bit in the binary number corresponds to an element in nums
. A bit set to 1 indicates the element is included in the subset, and 0 indicates exclusion.
Algorithm:
Iterate through all possible binary numbers from 0 to 2N - 1.
For each binary number (mask
):
t
).mask
. If the i
th bit is 1, add nums[i]
to t
.t
to the result (ans
).Time Complexity: O(N * 2N). Similar to DFS, we iterate through all subsets (2N), and building each subset takes O(N).
Space Complexity: O(N). Space is used to store the current subset and the result.
Code (Python3):
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
ans = []
for mask in range(1 << len(nums)):
subset = []
for i in range(len(nums)):
if (mask >> i) & 1:
subset.append(nums[i])
ans.append(subset)
return ans
Similar code can be written for other languages using bit manipulation. The core idea remains the same: iterate through all possible binary numbers and build subsets based on the bit representation.
Both approaches solve the problem efficiently. The choice between them often comes down to personal preference or coding style. The binary enumeration approach might appear slightly more concise in some languages.