C# Program to Find All Subsets of a Set
FindSubsets(int[] set, int index, List current, List> result)
.Examples
How to Think About It
Algorithm
Code
using System; using System.Collections.Generic; class Program { static void FindSubsets(int[] set, int index, List<int> current, List<List<int>> result) { if (index == set.Length) { result.Add(new List<int>(current)); return; } // Exclude current element FindSubsets(set, index + 1, current, result); // Include current element current.Add(set[index]); FindSubsets(set, index + 1, current, result); current.RemoveAt(current.Count - 1); } static void Main() { int[] set = {1, 2, 3}; var result = new List<List<int>>(); FindSubsets(set, 0, new List<int>(), result); foreach (var subset in result) { Console.WriteLine("[" + string.Join(", ", subset) + "]"); } } }
Dry Run
Let's trace the input [1, 2] through the code
Start recursion at index 0
Current subset: []
Exclude element 1 at index 0
Current subset: []
Exclude element 2 at index 1
Current subset: [] -> Add [] to result
Include element 2 at index 1
Current subset: [2] -> Add [2] to result
Include element 1 at index 0
Current subset: [1]
Exclude element 2 at index 1
Current subset: [1] -> Add [1] to result
Include element 2 at index 1
Current subset: [1, 2] -> Add [1, 2] to result
| Index | Current Subset | Action | Result List |
|---|---|---|---|
| 0 | [] | Exclude 1 | [] |
| 1 | [] | Exclude 2 | [[]] |
| 1 | [] | Include 2 | [[], [2]] |
| 0 | [] | Include 1 | [[], [2]] |
| 1 | [1] | Exclude 2 | [[], [2], [1]] |
| 1 | [1] | Include 2 | [[], [2], [1], [1, 2]] |
Why This Works
Step 1: Recursive branching
The code uses recursion to explore two paths at each element: including it or excluding it, which covers all subset possibilities.
Step 2: Base case
When the index reaches the length of the set, the current subset is complete and added to the result list.
Step 3: Backtracking
After including an element and recursing, the element is removed to restore the state for the next recursive call excluding it.
Alternative Approaches
using System; using System.Collections.Generic; class Program { static List<List<int>> FindSubsetsIterative(int[] set) { var result = new List<List<int>>(); int n = set.Length; int total = 1 << n; // 2^n subsets for (int i = 0; i < total; i++) { var subset = new List<int>(); for (int j = 0; j < n; j++) { if ((i & (1 << j)) != 0) { subset.Add(set[j]); } } result.Add(subset); } return result; } static void Main() { int[] set = {1, 2, 3}; var subsets = FindSubsetsIterative(set); foreach (var subset in subsets) { Console.WriteLine("[" + string.Join(", ", subset) + "]"); } } }
using System; using System.Collections.Generic; using System.Linq; class Program { static void Main() { int[] set = {1, 2, 3}; var subsets = set.Aggregate( new List<List<int>> { new List<int>() }, (acc, val) => acc.Concat(acc.Select(x => x.Concat(new[] { val }).ToList())).ToList() ); foreach (var subset in subsets) { Console.WriteLine("[" + string.Join(", ", subset) + "]"); } } }
Complexity: O(2^n * n) time, O(2^n * n) space
Time Complexity
There are 2^n subsets for a set of size n, and each subset can take up to O(n) time to copy or print, so total time is O(2^n * n).
Space Complexity
We store all subsets, which can be up to 2^n subsets each of size up to n, so space is O(2^n * n).
Which Approach is Fastest?
The iterative bit manipulation approach is generally faster due to less overhead, but recursion is easier to understand and implement.
| Approach | Time | Space | Best For |
|---|---|---|---|
| Recursive Backtracking | O(2^n * n) | O(2^n * n) | Clarity and teaching recursion |
| Iterative Bit Manipulation | O(2^n * n) | O(2^n * n) | Performance and bitwise operations |
| LINQ Aggregate | O(2^n * n) | O(2^n * n) | Concise code using functional style |