Starting from a finite set, we can construct pairs, sequences, subsets, and orderings. For each construction, these notes explain what counts as a distinct result, how many results exist, and the time and storage required to generate them.
Before counting, decide what makes two objects distinct: whether order matters and whether elements may be reused. Keep those rules fixed throughout the problem.
Let a finite set be:
A = {a, b, c}
|A| = n = 3
Unless stated otherwise,
Almost everything below is about creating new sets of objects from A, then counting how many objects there are, then generating them efficiently.
This is the same move you do in programming all the time, turning a small input into a space of candidates. If the candidate space is small, brute force can work. If it’s huge (hello,
When you see “choose one thing from here and one thing from there,” you’re in Cartesian-product land. It’s the formal version of nested loops: for each
The Cartesian product
A × B = {(x, y) : x ∈ A, y ∈ B}
Example diagram
Let:
A = {a, b, c}
B = {0, 1}
Then:
A × B =
(a,0) (a,1)
(b,0) (b,1)
(c,0) (c,1)
“grid view”:
B
0 1
A +-----+-----+
a | a,0 | a,1 |
+-----+-----+
b | b,0 | b,1 |
+-----+-----+
c | c,0 | c,1 |
+-----+-----+
Do remember these are ordered pairs,
If
|A × B| = n·m
This is the foundation of:
- counting multi-step choices (“choose x then choose y”)
- building tuples (records, coordinates)
- generating combinations/permutations via sequences (see below)
This shows up constantly in real code: if you ever wrote two nested loops, joined two tables, or enumerated coordinate pairs on a grid, you were living in
To enumerate all pairs you must output
- Time:
$\Theta(n\cdot m)$ - Space:
$\Theta(1)$ extra (if streaming) or$\Theta(n\cdot m)$ if storing
A practical do/don’t: do stream results when possible (generate and consume immediately), and don’t store the full product unless you truly need random access, memory can become your bottleneck before time does.
If Cartesian products feel like “two-loop land,” the power set is “every possible on/off choice.” This is where problems become exponential because you’re not picking one item, you’re deciding for each item whether it’s included.
The power set
For A={a,b,c}:
𝒫(A) = {
∅,
{a}, {b}, {c},
{a,b}, {a,c}, {b,c},
{a,b,c}
}
Subset lattice (Boolean lattice):
{a,b,c}
/ | \
{a,b} {a,c} {b,c}
| \ / \ / |
| / \ / \ |
{a} {b} {c}
\ | /
∅
The power set is the search space behind “try all subsets” algorithms, feature selection, subset sum, knapsack-style brute force, and lots of graph subset problems. The lattice diagram is the map of that search space.
If
|𝒫(A)| = 2^n
Why? Each element has two states: “in” or “out”.
Bitmask picture (n=3):
a b c subset
0 0 0 ∅
1 0 0 {a}
0 1 0 {b}
0 0 1 {c}
1 1 0 {a,b}
1 0 1 {a,c}
0 1 1 {b,c}
1 1 1 {a,b,c}
A do/don’t that saves headaches: do think “bitmask = subset” whenever you need to generate subsets efficiently, and don’t try to be “clever” by skipping the output cost, if the problem truly needs all subsets,
To enumerate all subsets, you must output
- Time:
$\Theta(n\cdot 2^n)$ if you build each subset explicitly (each subset may cost up to$n$ to construct) - Space:
$\Theta(n)$ recursion/bitmask state (streaming) or$\Theta(n\cdot 2^n)$ if storing all
The big idea: output size dominates. If you’re generating all subsets, you’re not “being slow”, you’re paying the bill for the number of results you asked to print.
Combinations are how you take the power set and say: “Okay, cool, but I only want subsets of a specific size.” This is a common “make the search space manageable” move: instead of every subset, you focus on one level of the lattice.
A
The set of all
{ S ⊆ A : |S| = k }
Counting (binomial coefficient)
If
C(n,k) = "n choose k" = n! / (k!(n-k)!)
In standard notation, this is
Do use
Combinations form a level in the power-set lattice.
For A={a,b,c}, k=2:
level k=2: {a,b} {a,c} {b,c}
Power set is all combinations across all k:
2^n = |𝒫(A)| = Σ_{k=0..n} C(n,k)
Proper math:
To enumerate all
- Time:
$\Theta(k \cdot C(n,k))$ (each output has$k$ items) - Space:
$O(k)$ working storage for a generator that recurses over chosen positions. A literal take/skip recursion over all$n$ input positions can instead need$O(n)$ stack space.
Common methods:
- recursive backtracking (“take / skip”)
- iterative lexicographic combination generation
- bitmask “next k-bit” tricks
One practical tip: do pick your generation method based on what you need downstream (lexicographic order? Streaming? Constant extra memory?), and don’t assume “combinations are always small”,
Permutations are what happens when you stop treating a chosen set as a bag of items and start treating it like a sequence. In algorithm terms, you’ve moved from “which items?” to “in what order?”
There are two closely related ideas:
All orderings of the entire set A (size
Count:
n!
Example A={a,b,c}:
abc acb bac bca cab cba
Ordered sequences of length
Count:
P(n,k) = n·(n-1)·...·(n-k+1) = n!/(n-k)!
Proper math:
A do/don’t that prevents classic mistakes: do decide early whether repetition is allowed; don’t mix “without repetition” formulas (like $P(n,k)$) with “with repetition” reasoning (like
For
start
├─ pick a ─┬─ then b => (a,b)
│ └─ then c => (a,c)
├─ pick b ─┬─ then a => (b,a)
│ └─ then c => (b,c)
└─ pick c ─┬─ then a => (c,a)
└─ then b => (c,b)
This tree is the “nested loops in your head” picture: each level is a choice, and the number of leaves is the total count.
A
P(n,k) = C(n,k) · k!
Proper math:
Because:
- choose the
$k$ elements (order-free):$\binom{n}{k}$ - order them:
$k!$
To enumerate all permutations of
- Time:
$\Theta(n \cdot n!)$ ($n$ work per permutation) - Space:
$\Theta(n)$ recursion or in-place swaps
Classic algorithms:
- Heap’s algorithm (efficient swaps)
- next_permutation (lexicographic)
- backtracking swap recursion
In-place swapping limits working memory, but explicitly producing every permutation still takes factorially many outputs. Estimate the output size before choosing enumeration, and use streaming when results can be processed one at a time.
Subsets and permutations without repetition use each element at most once. Cartesian products and powers allow the same value in different positions. If reuse is allowed, the counting flips in a really clean way: “no repetition” tends to create factorials and falling factorials; “repetition allowed” tends to create powers and stars-and-bars.
If you choose
A^k = A × A × ... × A (k times)
|A^k| = n^k
Proper Math:
This is exactly “all
Number of size-$k$ multisets from
C(n+k-1, k)
For
If you have
n^k
This use of “permutations with repetition” means unrestricted sequences. Arranging a fixed multiset is a different problem: if aab has three orderings: aab, aba, and baa.
This section is the “snap everything into place” moment. If you ever feel lost, switching to the function viewpoint usually makes the counting obvious: how many ways can I assign labels to inputs?
A subset
f: A → {0,1}
f(x)=1 if x∈S else 0
Number of such functions is
A length-$k$ sequence is:
g: {1..k} → A
Number is
A permutation is a bijection:
p: {1..n} → A
Number is
So:
- power set = all
${0,1}$ -labelings of$A$ - Cartesian powers = all
$k$ -position labelings by$A$ - permutations = all one-to-one labelings of positions by
$A$
A do/don’t here: do use “functions” when you’re stuck; don’t overcomplicate it with new symbols, this is meant to be the simplest mental model.
If you generate these objects, the minimum time is at least the number of outputs. The bounds below assume nonempty outputs are copied explicitly and each element takes constant time to copy. For
So:
| Structure | Count | Typical enumeration time |
|---|---|---|
|
|
||
| permutations | ||
|
|
||
|
|
This is why many problems that ask you to “try all subsets” are exponential: the search space literally has
One last do/don’t that matters in real projects: do treat these counts like early warning signs (a design review for your algorithm), and don’t wait until you’ve implemented everything to realize you built an
Ask two questions:
- No → combinations / subsets / multisets
- Yes → permutations / sequences / tuples
- No repetition → factorial / falling factorial
- Repetition allowed → powers / stars-and-bars
Decision table:
No repetition Repetition allowed
Order matters P(n,k)=n!/(n-k)! n^k
Order doesn't matter C(n,k)=n!/(k!(n-k)!) C(n+k-1,k)
Use the table by deciding first whether order matters and then whether elements can be reused. Those two assumptions distinguish the four counting problems.
- Power set / combinations show up in: subset-sum, knapsack variants, feature selection, graph vertex subsets, brute-force optimization.
- Permutations show up in: traveling salesman, scheduling, ordering constraints, anagrams, backtracking search.
- Cartesian products show up in: nested loops, join operations in databases, grid/coordinate enumeration, state spaces.
A yes/no branch for each of
A tiny “big picture” diagram: how they build on each other
Sets (A)
|
+--> Cartesian product (A×B) ---> tuples / coordinates
| |
| +--> Cartesian power (A^k) ---> sequences, strings (n^k)
|
+--> Power set P(A) ---> all subsets (2^n)
|
+--> k-subsets ---> combinations C(n,k)
|
+--> order them ---> k-permutations = C(n,k)·k! = P(n,k)