🧠
Brute Force (Pure Recursion Include/Exclude)
💡 This approach is the foundation for understanding subsets generation. It explicitly explores every possibility by deciding to include or exclude each element, which helps grasp the recursion tree structure.
Intuition
At each element, we have two choices: include it in the current subset or exclude it. Recursively applying this for all elements generates all subsets.
Algorithm
- Start from the first element and an empty current subset.
- At each step, recursively explore two paths: one including the current element, one excluding it.
- When all elements are processed, add the current subset to the result.
- Return the collected subsets after exploring all branches.
💡 The challenge is to visualize the recursion tree and understand how subsets accumulate at the leaves.
from typing import List
def subsets(nums: List[int]) -> List[List[int]]:
result = []
def backtrack(index: int, path: List[int]):
if index == len(nums):
result.append(path[:])
return
# Exclude current element
backtrack(index + 1, path)
# Include current element
path.append(nums[index])
backtrack(index + 1, path)
path.pop()
backtrack(0, [])
return result
# Driver code
if __name__ == '__main__':
print(subsets([1,2,3]))
Line Notes
def subsets(nums: List[int]) -> List[List[int]]:Defines the main function with type hints for clarity
result = []Stores all subsets generated during recursion
def backtrack(index: int, path: List[int]):Helper recursive function tracking current index and current subset path
if index == len(nums):Base case: all elements processed, add current subset to result
result.append(path[:])Append a copy of current subset to avoid mutation issues
backtrack(index + 1, path)Explore path including the current element
path.append(nums[index])Include current element before next recursion
path.pop()Backtrack by removing last element to restore state
import java.util.*;
public class Subsets {
public static List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
backtrack(nums, 0, new ArrayList<>(), result);
return result;
}
private static void backtrack(int[] nums, int index, List<Integer> path, List<List<Integer>> result) {
if (index == nums.length) {
result.add(new ArrayList<>(path));
return;
}
// Exclude current element
backtrack(nums, index + 1, path, result);
// Include current element
path.add(nums[index]);
backtrack(nums, index + 1, path, result);
path.remove(path.size() - 1);
}
public static void main(String[] args) {
System.out.println(subsets(new int[]{1,2,3}));
}
}
Line Notes
public static List<List<Integer>> subsets(int[] nums)Main function signature returning list of subsets
List<List<Integer>> result = new ArrayList<>();Stores all subsets generated
backtrack(nums, 0, new ArrayList<>(), result);Start recursion from index 0 with empty path
if (index == nums.length)Base case: all elements processed, add current subset
result.add(new ArrayList<>(path));Add a copy of current subset to result
backtrack(nums, index + 1, path, result);Explore path including the current element
path.add(nums[index]);Include current element before recursion
path.remove(path.size() - 1);Backtrack to restore path state
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
void backtrack(vector<int>& nums, int index, vector<int>& path, vector<vector<int>>& result) {
if (index == nums.size()) {
result.push_back(path);
return;
}
// Exclude current element
backtrack(nums, index + 1, path, result);
// Include current element
path.push_back(nums[index]);
backtrack(nums, index + 1, path, result);
path.pop_back();
}
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> result;
vector<int> path;
backtrack(nums, 0, path, result);
return result;
}
};
int main() {
Solution sol;
vector<int> nums = {1,2,3};
vector<vector<int>> res = sol.subsets(nums);
for (auto &subset : res) {
cout << "[";
for (int i = 0; i < subset.size(); i++) {
cout << subset[i];
if (i != subset.size() - 1) cout << ",";
}
cout << "] ";
}
cout << endl;
return 0;
}
Line Notes
void backtrack(vector<int>& nums, int index, vector<int>& path, vector<vector<int>>& result)Recursive helper with current index and subset path
if (index == nums.size())Base case: all elements processed, add current subset
result.push_back(path);Add current subset to result vector
backtrack(nums, index + 1, path, result);Explore path including the current element
path.push_back(nums[index]);Include current element before recursion
path.pop_back();Backtrack to restore path state
int main()Driver code to test the subsets function
function subsets(nums) {
const result = [];
function backtrack(index, path) {
if (index === nums.length) {
result.push([...path]);
return;
}
// Exclude current element
backtrack(index + 1, path);
// Include current element
path.push(nums[index]);
backtrack(index + 1, path);
path.pop();
}
backtrack(0, []);
return result;
}
// Test
console.log(subsets([1,2,3]));
Line Notes
function subsets(nums) {Defines main function to generate subsets
const result = [];Stores all subsets generated
function backtrack(index, path) {Recursive helper tracking index and current subset
if (index === nums.length) {Base case: all elements processed, add current subset
result.push([...path]);Add a copy of current subset to result
backtrack(index + 1, path);Explore path including the current element
path.push(nums[index]);Include current element before recursion
path.pop();Backtrack to restore path state
TimeO(2^n * n)
SpaceO(n) recursion stack + O(2^n * n) output
Each element has two choices, leading to 2^n subsets. Copying each subset of average length n leads to O(2^n * n) time and space.
💡 For n=20, 2^20 is about 1 million subsets, so this approach is feasible but can be slow for larger n.
Interview Verdict: Accepted
Though exponential, this brute force approach is the standard solution for generating all subsets and is accepted for typical input sizes.