Power Set
Algorithm · Core
Step through it: Backtracking pattern
holding [ ]
Step 1 of 7
Goal: list every subset of [1, 2]. At each number we choose: take it or skip it. All the choices form a tree of decisions. Start with nothing chosen.
Step 2 of 7
Choose to take 1.
Step 3 of 7
Take 2 as well. No numbers are left, so write this subset down.
Step 4 of 7
Undo that last choice (the "back" in backtracking) and skip 2 instead. Write [1] down.
Step 5 of 7
Undo taking 1 and skip it instead: holding [ ] again.
Step 6 of 7
From [ ], take or skip 2 the same way: [2] and [ ]. The four leaves are every subset. The recipe is choose, explore, undo. If a choice breaks a rule, stop that branch early (pruning).
Step 7 of 7
Your turn.
Power set of a set S is the set of all of the subsets of S, including the
empty set and S itself. Power set of set S is denoted as P(S).
For example for {x, y, z}, the subsets
are:
{
{}, // (also denoted empty set ∅ or the null set)
{x},
{y},
{z},
{x, y},
{x, z},
{y, z},
{x, y, z}
}Here is how we may illustrate the elements of the power set of the set {x, y, z} ordered with respect to
inclusion:
Number of Subsets
If S is a finite set with |S| = n elements, then the number of subsets
of S is |P(S)| = 2^n. This fact, which is the motivation for the
notation 2^S, may be demonstrated simply as follows:
First, order the elements of
Sin any manner. We write any subset ofSin the format{γ1, γ2, ..., γn}whereγi , 1 ≤ i ≤ n, can take the value of0or1. Ifγi = 1, thei-th element ofSis in the subset; otherwise, thei-th element is not in the subset. Clearly the number of distinct subsets that can be constructed this way is2^nasγi ∈ {0, 1}.
Algorithms
Bitwise Solution
Each number in binary representation in a range from 0 to 2^n does exactly
what we need: it shows by its bits (0 or 1) whether to include related
element from the set or not. For example, for the set {1, 2, 3} the binary
number of 0b010 would mean that we need to include only 2 to the current set.
abc | Subset | |
|---|---|---|
0 | 000 | {} |
1 | 001 | {c} |
2 | 010 | {b} |
3 | 011 | {c, b} |
4 | 100 | {a} |
5 | 101 | {a, c} |
6 | 110 | {a, b} |
7 | 111 | {a, b, c} |
See bwPowerSet.js file for bitwise solution.
Backtracking Solution
In backtracking approach we're constantly trying to add next element of the set to the subset, memorizing it and then removing it and try the same with the next element.
See btPowerSet.js file for backtracking solution.
Cascading Solution
This is, arguably, the simplest solution to generate a Power Set.
We start with an empty set:
powerSets = [[]]Now, let's say:
originalSet = [1, 2, 3]Let's add the 1st element from the originalSet to all existing sets:
[[]] ← 1 = [[], [1]]Adding the 2nd element to all existing sets:
[[], [1]] ← 2 = [[], [1], [2], [1, 2]]Adding the 3nd element to all existing sets:
[[], [1], [2], [1, 2]] ← 3 = [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]And so on, for the rest of the elements from the originalSet. On every iteration the number of sets is doubled, so we'll get 2^n sets.
See caPowerSet.js file for cascading solution.
Code
/**
* @param {*[]} originalSet - Original set of elements we're forming power-set of.
* @param {*[][]} allSubsets - All subsets that have been formed so far.
* @param {*[]} currentSubSet - Current subset that we're forming at the moment.
* @param {number} startAt - The position of in original set we're starting to form current subset.
* @return {*[][]} - All subsets of original set.
*/
function btPowerSetRecursive(originalSet, allSubsets = [[]], currentSubSet = [], startAt = 0) {
// Let's iterate over originalSet elements that may be added to the subset
// without having duplicates. The value of startAt prevents adding the duplicates.
for (let position = startAt; position < originalSet.length; position += 1) {
// Let's push current element to the subset
currentSubSet.push(originalSet[position]);
// Current subset is already valid so let's memorize it.
// We do array destruction here to save the clone of the currentSubSet.
// We need to save a clone since the original currentSubSet is going to be
// mutated in further recursive calls.
allSubsets.push([...currentSubSet]);
// Let's try to generate all other subsets for the current subset.
// We're increasing the position by one to avoid duplicates in subset.
btPowerSetRecursive(originalSet, allSubsets, currentSubSet, position + 1);
// BACKTRACK. Exclude last element from the subset and try the next valid one.
currentSubSet.pop();
}
// Return all subsets of a set.
return allSubsets;
}
/**
* Find power-set of a set using BACKTRACKING approach.
*
* @param {*[]} originalSet
* @return {*[][]}
*/
export default function btPowerSet(originalSet) {
return btPowerSetRecursive(originalSet);
}/**
* Find power-set of a set using BITWISE approach.
*
* @param {*[]} originalSet
* @return {*[][]}
*/
export default function bwPowerSet(originalSet) {
const subSets = [];
// We will have 2^n possible combinations (where n is a length of original set).
// It is because for every element of original set we will decide whether to include
// it or not (2 options for each set element).
const numberOfCombinations = 2 ** originalSet.length;
// Each number in binary representation in a range from 0 to 2^n does exactly what we need:
// it shows by its bits (0 or 1) whether to include related element from the set or not.
// For example, for the set {1, 2, 3} the binary number of 0b010 would mean that we need to
// include only "2" to the current set.
for (let combinationIndex = 0; combinationIndex < numberOfCombinations; combinationIndex += 1) {
const subSet = [];
for (let setElementIndex = 0; setElementIndex < originalSet.length; setElementIndex += 1) {
// Decide whether we need to include current element into the subset or not.
if (combinationIndex & (1 << setElementIndex)) {
subSet.push(originalSet[setElementIndex]);
}
}
// Add current subset to the list of all subsets.
subSets.push(subSet);
}
return subSets;
}/**
* Find power-set of a set using CASCADING approach.
*
* @param {*[]} originalSet
* @return {*[][]}
*/
export default function caPowerSet(originalSet) {
// Let's start with an empty set.
const sets = [[]];
/*
Now, let's say:
originalSet = [1, 2, 3].
Let's add the first element from the originalSet to all existing sets:
[[]] ← 1 = [[], [1]]
Adding the 2nd element to all existing sets:
[[], [1]] ← 2 = [[], [1], [2], [1, 2]]
Adding the 3nd element to all existing sets:
[[], [1], [2], [1, 2]] ← 3 = [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
And so on for the rest of the elements from originalSet.
On every iteration the number of sets is doubled, so we'll get 2^n sets.
*/
for (let numIdx = 0; numIdx < originalSet.length; numIdx += 1) {
const existingSetsNum = sets.length;
for (let setIdx = 0; setIdx < existingSetsNum; setIdx += 1) {
const set = [...sets[setIdx], originalSet[numIdx]];
sets.push(set);
}
}
return sets;
}References
Practice
Backtracking
- Binary Tree PathsEasy
- SubsetsMedium
- Subsets IIMedium
- PermutationsMedium
- Permutations IIMedium
- CombinationsMedium
- Combination SumMedium
- Combination Sum IIMedium
- Combination Sum IIIMedium
- Letter Combinations of a Phone NumberMedium
- Generate ParenthesesMedium
- Palindrome PartitioningMedium
- Word SearchMedium
- Restore IP AddressesMedium
- Letter Case PermutationMedium
- Partition to K Equal Sum SubsetsMedium
- N-QueensHard
- Sudoku SolverHard
- Word Break IIHard
- Expression Add OperatorsHard
Spotted a mistake or something unclear? Report it on GitHub.