Java Program to Find All Subsets of a Set
findSubsets(int[] arr, int index, List current) .Examples
How to Think About It
Algorithm
Code
import java.util.*; public class Subsets { public static void findSubsets(int[] arr, int index, List<Integer> current, List<List<Integer>> result) { if (index == arr.length) { result.add(new ArrayList<>(current)); return; } // Exclude current element findSubsets(arr, index + 1, current, result); // Include current element current.add(arr[index]); findSubsets(arr, index + 1, current, result); current.remove(current.size() - 1); } public static void main(String[] args) { int[] arr = {1, 2, 3}; List<List<Integer>> result = new ArrayList<>(); findSubsets(arr, 0, new ArrayList<>(), result); System.out.println(result); } }
Dry Run
Let's trace the input [1, 2] through the code
Start with empty subset and index 0
current = [], index = 0
Exclude element 1, move to index 1
current = [], index = 1
Exclude element 2, move to index 2 (end)
current = [], index = 2 -> add [] to result
Include element 2, move to index 2 (end)
current = [2], index = 2 -> add [2] to result
Back to index 0, include element 1
current = [1], index = 1
Exclude element 2, move to index 2 (end)
current = [1], index = 2 -> add [1] to result
Include element 2, move to index 2 (end)
current = [1, 2], index = 2 -> add [1, 2] to result
| Index | Current Subset | Action |
|---|---|---|
| 0 | [] | Exclude 1 |
| 1 | [] | Exclude 2 |
| 2 | [] | Add [] |
| 1 | [] | Include 2 |
| 2 | [2] | Add [2] |
| 0 | [] | Include 1 |
| 1 | [1] | Exclude 2 |
| 2 | [1] | Add [1] |
| 1 | [1] | Include 2 |
| 2 | [1, 2] | Add [1, 2] |
Why This Works
Step 1: Recursive Choice
At each element, the code chooses to either include or exclude it, exploring all paths.
Step 2: Base Case
When the index reaches the array length, the current subset is complete and added to the result.
Step 3: Backtracking
After exploring including an element, the code removes it to try other combinations without it.
Alternative Approaches
import java.util.*; public class SubsetsIterative { public static List<List<Integer>> subsets(int[] nums) { List<List<Integer>> result = new ArrayList<>(); int n = nums.length; int total = 1 << n; // 2^n subsets for (int i = 0; i < total; i++) { List<Integer> subset = new ArrayList<>(); for (int j = 0; j < n; j++) { if ((i & (1 << j)) != 0) { subset.add(nums[j]); } } result.add(subset); } return result; } public static void main(String[] args) { int[] arr = {1, 2, 3}; System.out.println(subsets(arr)); } }
import java.util.*; import java.util.stream.*; public class SubsetsStream { public static List<List<Integer>> subsets(int[] nums) { return IntStream.range(0, 1 << nums.length) .mapToObj(i -> IntStream.range(0, nums.length) .filter(j -> (i & (1 << j)) != 0) .mapToObj(j -> nums[j]) .collect(Collectors.toList())) .collect(Collectors.toList()); } public static void main(String[] args) { int[] arr = {1, 2, 3}; System.out.println(subsets(arr)); } }
Complexity: O(n * 2^n) time, O(n * 2^n) space
Time Complexity
There are 2^n subsets for n elements, and each subset can take up to O(n) time to copy or print.
Space Complexity
The result stores all subsets, requiring O(n * 2^n) space; recursion stack uses O(n) space.
Which Approach is Fastest?
Iterative bit manipulation is generally faster than recursion but less intuitive; recursion is easier to understand.
| Approach | Time | Space | Best For |
|---|---|---|---|
| Recursive Backtracking | O(n * 2^n) | O(n * 2^n) | Easy to understand and implement |
| Iterative Bit Manipulation | O(n * 2^n) | O(n * 2^n) | Performance and bitwise practice |
| Java Streams | O(n * 2^n) | O(n * 2^n) | Concise functional style |