Back to Combinatorics

Chapter 5: Algorithmic Generation of Combinatorial Structures

October 10, 2026 Wasil Zafar 25 min read

Generate each object once, then rank, unrank, and sample it

Table of Contents

  1. Can every subset be visited by changing one bit?
  2. 5.1 Generating permutations and inversions
  3. 5.2 Generating combinations and subsets
  4. 5.3 Lexicographic and Gray-code orderings
  5. 5.4 Backtracking and recursive generation
  6. 5.5 Ranking and unranking combinatorial objects
  7. 5.6 Combinatorial number systems
  8. 5.7 Constant-amortized-time generation
  9. 5.8 Uniform random sampling of finite structures
  10. Build and verify a rank–unrank round trip

Can Every Subset Be Visited by Changing One Bit?

Suppose a system has three optional features. A configuration is a subset of these features, or equivalently a 3-bit binary word: a $1$ means a feature is enabled, and a $0$ means it is disabled. There are $2^3 = 8$ total configurations.

Ordinary binary counting visits all eight configurations in numerical sequence ($000 \to 001 \to 010 \to 011 \to \dots$), but the transition from $011$ to $100$ flips all three bits simultaneously. In physical hardware, sensors, and state machines, multi-bit transitions cause glitching, power spikes, and timing race conditions. Can we visit every configuration exactly once while changing only one feature at a time?

The Power of Gray Codes

A Gray code is a minimal-change ordering of binary words where every consecutive pair differs in exactly one bit position. Gray codes allow testing frameworks to update features incrementally, rotary encoders to read positions without digital noise, and algorithms to update combinatorial state in $O(1)$ time per step.

Binary Counting vs Gray Code Minimal-Change Path Diagram comparing 3-bit binary counting which has multi-bit jumps versus Gray Code which changes exactly 1 bit per step. Standard Binary Counting vs. Reflected Gray Code Standard Binary Counter Unpredictable Bit Flips (Up to 3 per step) 001 ⟶ (2 bit flips) 010 011 💥 3 BIT FLIPS! 100 111 ⟶ (3 bit flips) 000 High glitch risk & electrical noise 3-Bit Gray Code Cycle Minimal Change: Exactly 1 Bit Flip per Step 000 ➔ 001 ➔ 011 ➔ 010 ↓ (1 bit) ↑ (1 bit) 100  101  111  110 ✓ Smooth 1-bit transitions throughout cycle
Figure 5.1: Gray code eliminates multi-bit jumps by ensuring every adjacent pair (and the cyclic loop back to start) differs in exactly one bit position.

This chapter explores algorithmic generation across four foundational tasks:

The Four Pillars of Combinatorial Algorithms Diagram showing Generation, Ranking, Unranking, and Sampling as the four fundamental algorithmic tasks. The Four Core Tasks of Combinatorial Algorithms 1. Generation List All Objects Sequence S₀, S₁, ..., Sₘ₋₁ No skips, no duplicates 2. Ranking Object ➔ Index rank(Object) = R Find position in order 3. Unranking Index ➔ Object unrank(R) = Object Direct access by index 4. Sampling Random Pick P(Object) = 1/M Uniform random selection
Figure 5.2: Knowing how many objects exist establishes the domain size $M$, but distinct algorithms are required for generation, ranking, unranking, and sampling.
Motivating Problem

3-Bit Reflected Gray Code Walk

Using three features $A, B, C$ represented by the left, middle, and right bits respectively:

Step Binary Word Enabled Features Subset Feature Changed From Previous Step
0000$\varnothing$Start
1001$\{C\}$Feature C
2011$\{B, C\}$Feature B
3010$\{B\}$Feature C
4110$\{A, B\}$Feature A
5111$\{A, B, C\}$Feature C
6101$\{A, C\}$Feature B
7100$\{A\}$Feature C

Notice that Step 7 (100) and Step 0 (000) also differ in exactly one bit ($A$), forming a complete Hamiltonian cycle on the 3D hypercube!


5.1 Generating Permutations and Inversions

A permutation of $n$ distinct symbols is an ordered arrangement of those symbols. For $\{1, 2, 3\}$, the six permutations listed in standard dictionary (lexicographic) order are:

$$123, \quad 132, \quad 213, \quad 231, \quad 312, \quad 321$$

Lexicographic order compares two sequences at their first differing position from left to right. Whichever sequence possesses the smaller symbol at that position appears first.

Narayana Pandita's Next-Permutation Algorithm

Often attributed to the 14th-century Indian mathematician Pandita Narayana, this algorithm finds the immediate lexicographic successor of a sequence of distinct ordered elements in $O(n)$ time. The steps below use 1-based positions $a_1,\dots,a_n$; the equivalent 0-based array notation is $A[0],\dots,A[n-1]$.

  1. Find Pivot: Locate the rightmost position $i$ such that $a_i < a_{i+1}$. (If no such $i$ exists, the entire sequence is decreasing, and generation is complete).
  2. Find Swap Target: In the strictly decreasing suffix to the right of position $i$, locate the rightmost position $j$ where $a_j > a_i$.
  3. Swap: Swap elements $a_i$ and $a_j$.
  4. Reverse Suffix: Reverse the entire suffix starting at position $i+1$ to make it strictly increasing (yielding the smallest possible suffix).

Worked example (0-based indexing). Starting with $A=[1,3,5,4,2]$, scan from the right to find the last ascent: $A[1]=3 < A[2]=5$, so the pivot is $i=1$. The suffix $[5,4,2]$ is decreasing. Scanning it from the right, the first value greater than $3$ is $A[3]=4$, so $j=3$. Swap the pivot and target to get $[1,4,5,3,2]$, then reverse the suffix after index $1$, $[5,3,2]$, to obtain the successor $[1,4,2,3,5]$.

Step-by-Step Next-Permutation Execution Visualizer Diagram demonstrating the 4 steps of Narayana Pandita's algorithm converting permutation 1 3 5 4 2 into its immediate successor 1 4 2 3 5. Step-by-Step Transition: Permutation (1, 3, 5, 4, 2) ➔ (1, 4, 2, 3, 5) Step 1: Find Rightmost Pivot (aᵢ < aᵢ₊₁): 1 3 5 4 2 Pivot = 3 (Suffix 5,4,2 decreasing) Step 2: Find Rightmost Element > Pivot: 1 3 5 4 2 Target = 4 (Smallest item > 3) Step 3: Swap Pivot (3) and Target (4): 1 4 5 3 2 Yields (1, 4, 5, 3, 2) Step 4: Reverse Suffix (5, 3, 2) ➔ (2, 3, 5): 1 4 2 3 5 Successor: (1, 4, 2, 3, 5) ✓
Figure 5.3: Narayana Pandita's algorithm minimal-increment step: swapping the pivot with the next-smallest viable element and reversing the suffix guarantees the exact immediate lexicographic successor.

Why is this the exact immediate successor? The suffix following the rightmost pivot $i$ is strictly decreasing, making it the maximum possible arrangement of those specific symbols. Any larger permutation must alter the pivot or an earlier position. Replacing $a_i$ with the smallest strictly larger symbol in the suffix makes the increase minimal. Reversing the suffix afterwards turns it from strictly decreasing to strictly increasing, producing the smallest possible prefix extension. In inversion terms, this changes the suffix from having all $\binom{m}{2}$ possible internal inversions (where $m$ is its length) to having none; this local change helps minimize the successor's remaining suffix.

Last permutation. If no pivot exists, the whole sequence is decreasing, which is the final permutation in lexicographic order. For example, reversing $[5,4,3,2,1]$ returns the first permutation $[1,2,3,4,5]$ when generation is meant to cycle.


Inversions and Minimum Adjacent Swaps

An inversion of a permutation $\pi = (\pi_1, \pi_2, \dots, \pi_n)$ is an out-of-order pair of positions $(i, j)$ where $i < j$ but $\pi_i > \pi_j$. The total number of inversions is denoted $\operatorname{inv}(\pi)$.

In simple terms: an inversion is a pair of values that appears in the wrong order: the larger value comes first. In an increasing list such as $(1,2,3,4)$, there are no inversions. To check a pair, look at their order in the permutation: if $x$ appears before $y$ and $x>y$, that pair is an inversion.

Concrete example. For $\pi=(3,1,4,2)$, compare each pair from left to right:

  • $3$ appears before $1$, and $3>1$: inversion $(3,1)$.
  • $3$ appears before $4$, but $3<4$: not an inversion.
  • $3$ appears before $2$, and $3>2$: inversion $(3,2)$.
  • $1$ appears before $4$ and before $2$, and is smaller than both: neither pair is an inversion.
  • $4$ appears before $2$, and $4>2$: inversion $(4,2)$.

Thus the inversion pairs are $(3,1)$, $(3,2)$, and $(4,2)$, so $\operatorname{inv}(\pi)=3$.

Permutation Inversions and Crossing Lines Visualizer Diagram showing crossing lines for permutation (3, 1, 4, 2) yielding 3 total inversions and matching the minimum adjacent swaps needed to sort it. Inversions in Permutation π = (3, 1, 4, 2): inv(π) = 3 Crossing-Lines Representation 1 2 3 4 3 1 4 2 3 Line Crossings = 3 Inversions Inversion Pairs & Sorting Path Inversion Pairs: (3,1), (3,2), (4,2) Adjacent Swap Sorting (3 Swaps): (3, 1, 4, 2) ➔ swap(3,1) (1, 3, 4, 2) ➔ swap(4,2) (1, 3, 2, 4) ➔ swap(3,2) ➔ (1, 2, 3, 4) ✓
Figure 5.4: Every inversion represents a pair of elements out of order. Swapping an adjacent inverted pair removes exactly one inversion, making $\operatorname{inv}(\pi)$ equal to the minimum number of adjacent swaps required to sort the permutation.

A direct inversion counter checks all $\binom{n}{2}$ position pairs in $O(n^2)$ time. The count ranges from $0$ for the increasing permutation to $\binom{n}{2}=\frac{n(n-1)}{2}$ for the decreasing permutation: there are $\binom{n}{2}$ pairs in total, and in descending order every pair is inverted. Swapping an adjacent inverted pair reduces $\operatorname{inv}(\pi)$ by exactly one without affecting any other pairwise relationships. Repeating such swaps sorts the sequence, so the inversion count is exactly the minimum number of adjacent swaps needed to sort it; Bubble Sort, for example, performs that many swaps when it swaps adjacent out-of-order elements until sorted.

$$\operatorname{inv}(\pi)=\text{minimum number of adjacent swaps needed to sort }\pi$$

Inversion count and lexicographic order describe different things. For $n=3$, the lexicographic list has inversion counts $0,1,1,2,2,3$ for $(1,2,3),(1,3,2),(2,1,3),(2,3,1),(3,1,2),(3,2,1)$. The endpoints are always the increasing permutation with zero inversions and the decreasing permutation with the maximum, but the count does not generally increase at every lexicographic step: for $n=4$, $(1,4,3,2)$ has $3$ inversions and its lexicographic successor $(2,1,3,4)$ has only $1$.

Connection to the immediate successor. Narayana's next-permutation algorithm (described above) finds the rightmost ascent, swaps its pivot with the smallest larger value to its right, then reverses the suffix. That suffix is initially decreasing, so a suffix of length $m$ has all $\binom{m}{2}$ possible internal inversions; after reversal it is increasing and has none. For the earlier example, the suffix $[5,3,2]$ after the swap has $3$ internal inversions, while $[2,3,5]$ after reversal has $0$. This reversal makes the tail as small as possible after the pivot is increased, which is why the result is the immediate lexicographic successor. It does not mean inversion counts always rise as we advance lexicographically—the $n=4$ counterexample above shows they can fall.

Worked Exercise

Inversion Count and Successor Practice

Problem: Find the inversion count of $\pi = (2, 4, 1, 3)$ and locate its immediate next lexicographic permutation.

  • Inversions: Pairwise comparisons where $i < j$ but $\pi_i > \pi_j$:
    • Element $2$: precedes $1 \implies \mathbf{1 \text{ inversion }} (2,1)$.
    • Element $4$: precedes $1$ and $3 \implies \mathbf{2 \text{ inversions }} (4,1), (4,3)$.
    • Element $1$: precedes $3 \implies \mathbf{0 \text{ inversions}}$.
    • Total Inversions: $1 + 2 + 0 = \mathbf{3}$.
  • Next Permutation:
    1. The rightmost ascent is $1 < 3$ at positions $3$ and $4$ (1-based), so the pivot is $1$.
    2. The rightmost value greater than the pivot is $3$.
    3. Swap $1$ and $3$ to obtain $(2, 4, 3, 1)$; the suffix after the pivot has one element, so reversing it leaves the sequence unchanged.
    4. Successor: $\mathbf{(2, 4, 3, 1)}$.

5.2 Generating combinations and subsets

Represent a $k$-element subset of $\{0,1,\ldots,n-1\}$ by its increasing tuple

$$ 0\le c_1\lt c_2\lt\cdots\lt c_k\le n-1. $$

Increasing tuples remove duplicates caused by different orders of the same selected elements. For $1\le k\le n$, the first tuple is $(0,1,\ldots,k-1)$ and the last is $(n-k,n-k+1,\ldots,n-1)$.

To find the next tuple in lexicographic order, scan from right to left for the largest index $i$ satisfying

$$ c_i\lt n-k+i-1. $$

Increment $c_i$ by one. Then reset every later coordinate to the smallest possible value: $c_{i+1}=c_i+1$, $c_{i+2}=c_i+2$, and so on. If no coordinate can increase, stop.

The upper bound on $c_i$ reserves enough larger elements for the remaining $k-i$ positions. Choosing the rightmost movable coordinate keeps the longest possible prefix unchanged. Resetting the suffix to its minimum produces the immediate successor, so the generator is complete and has no duplicates.

For $k=0$, there is one combination, the empty tuple. If $k\gt n$ or $k\lt0$, there are no combinations. These cases should be handled before attempting to scan tuple positions.

All subsets. A subset can also be represented by an $n$-bit membership word. Enumerating every binary word lists all $2^n$ subsets. Alternatively, generate combinations separately for sizes $k=0,1,\ldots,n$; these classes are disjoint and contain $\sum_k\binom nk=2^n$ objects altogether.

Proof Workshop

Generating combinations and subsets: a worked example

Problem. Generate all two-element subsets of $\{0,1,2,3\}$ in lexicographic order.

Current tupleSuccessor actionNext tuple
$(0,1)$Increment the last coordinate$(0,2)$
$(0,2)$Increment the last coordinate$(0,3)$
$(0,3)$Increment the first coordinate; reset the suffix$(1,2)$
$(1,2)$Increment the last coordinate$(1,3)$
$(1,3)$Increment the first coordinate; reset the suffix$(2,3)$
$(2,3)$No coordinate can increaseStop

Every tuple is increasing, so it represents a legal subset exactly once. Every two-element subset occurs because the listed sequence takes immediate successors from the smallest tuple to the largest. There are six outputs, agreeing with $\binom42=6$.

The transition $(0,3)\to(1,2)$ changes both selected elements. Lexicographic combination order is therefore not automatically a minimal-change order.

Exercise. Find the successor of $(0,2,4)$ among three-element subsets of $\{0,1,2,3,4\}$. Hint: The last coordinate is already at its maximum. Answer: $(0,3,4)$.

Scanning and resetting the tuple takes $O(k)$ time in the worst case per successor, with $O(1)$ auxiliary space beyond the tuple. Copying an output requires $O(k)$ time. These bounds describe this straightforward generator; they do not assert that every transition needs to change all coordinates.

5.3 Lexicographic and Gray-code Orderings

For fixed-length binary words of length $n$, lexicographic order (using $0 < 1$) matches ordinary binary numerical order. For length $n = 3$, counting in lexicographic order yields: $000, 001, 010, 011, 100, 101, 110, 111$. However, a Gray code ordering visits the exact same set of $2^n$ binary words in a sequence where every adjacent pair differs by only a single bit.

The total number of differing bit positions between two binary words $a$ and $b$ is called their Hamming distance, denoted $H(a, b)$. While lexicographic order suffers from jumps with Hamming distances up to $n$ (such as $011 \to 100$ where $H(011, 100) = 3$), a Gray code guarantees $H(g_i, g_{i+1}) = 1$ for all steps $i$.

Recursive Reflected Construction ($G_n$)

Let $G_0 = (\varepsilon)$ contain only the empty word. To construct the Gray code sequence $G_n$ of length $n$ from $G_{n-1}$:

  1. Take the sequence $G_{n-1}$ and prefix a 0 to every word in its original forward order.
  2. Take the sequence $G_{n-1}$ in reversed order ($G_{n-1}^R$) and prefix a 1 to every word.
  3. Concatenate the two halves to form $G_n$.

Start with $G_0=(\varepsilon)$, where $\varepsilon$ is the empty word. Prefixing $0$ and $1$ gives $G_1=(0,1)$. Reflecting $G_1$ gives $G_1^R=(1,0)$, so the two prefixed halves are $(00,01)$ and $(11,10)$; hence $G_2=(00,01,11,10)$.

Building $G_3$ from $G_2$. Keep $G_2=(00,01,11,10)$ in its original order for the first half and prefix each word with $0$: $(000,001,011,010)$. Reverse $G_2$ to get $(10,11,01,00)$, prefix each word with $1$ for the reflected half $(110,111,101,100)$, then concatenate:

$$G_3=(000,001,011,010,110,111,101,100).$$

For instance, the middle transition is $010\to110$: the two words share the same tail $10$, so only the new leading bit changes.

Recursive Reflected Construction of Gray Code Diagram showing how G3 is constructed by prefixing 0 to G2 and prefixing 1 to the reversed G2 across a reflection axis. Recursive Reflected Construction: Building G₃ from G₂ Top Half: Prefix '0' + G₂ Original Order 000 001 011 010 G₂ = (00, 01, 11, 10) 🪞 Reflection Axis Bottom Half: Prefix '1' + G₂ Reversed 110 111 101 100 Reversed G₂ = (10, 11, 01, 00) Complete Sequence G₃ (8 Words) Step 0: 000 Step 1: 001 Step 2: 011 Step 3: 010 Step 4: 110 Step 5: 111 Step 6: 101 Step 7: 100 ✓ Join (Step 3➔4): 010 ➔ 110 (1 bit flip) ✓ Loop (Step 7➔0): 100 ➔ 000 (1 bit flip)
Figure 5.5: Reflected construction of $G_3$: Prepending $0$ to $G_2$ and $1$ to reversed $G_2$ guarantees that every adjacent transition (and the wrap-around) differs in exactly 1 bit position.

Why does reflection guarantee Hamming distance $1$? The construction preserves the one-bit-change property inside each half and makes the join between the halves differ in only the leading bit:

  • Base case: $G_1=(0,1)$ contains both one-bit words, and the only transition changes one bit.
  • Completeness and uniqueness: The first half contains every $(n-1)$-bit word prefixed by $0$ exactly once. The second contains every such word prefixed by $1$ exactly once. The halves cannot overlap because their first bits differ, so together they contain all $2^n$ words.
  • Inside either half: The prefixed bit is constant, so it contributes no difference between neighboring words. The remaining bits follow $G_{n-1}$ or the same sequence in reverse; reversing order does not change the Hamming distance of neighboring pairs.
  • At the join: The last word of the first half is $0+\operatorname{last}(G_{n-1})$. Since the second half uses the reversed list, its first word is $1+\operatorname{last}(G_{n-1})$. Their tails match exactly, so only the leading bit changes.
  • Wrap-around (cyclic version): The first word is $0+\operatorname{first}(G_{n-1})=0^n$. The last word is $1+\operatorname{first}(G_{n-1})=10^{n-1}$, because reflection puts the original first word last. These differ only in the leading bit, so the last and first words also have Hamming distance $1$.

Direct Closed-Form Computation: $g(i) = i \oplus \lfloor i / 2 \rfloor$

Instead of recursively constructing previous lists, we can compute the Gray code word at step $i$ ($0 \le i < 2^n$) directly in $O(1)$ time using bitwise operations:

$$g(i) = i \mathbin{\oplus} \left\lfloor \frac{i}{2} \right\rfloor = i \mathbin{\oplus} (i \gg 1)$$

where $\oplus$ represents the bitwise exclusive OR (XOR) operation, which outputs 1 wherever two input bits differ and 0 where they match. Dividing by 2 is equivalent to a logical right shift by 1 bit.

Bitwise Gray Code Formula Visualizer Diagram showing the calculation of g(6) = 6 XOR (6 shift right 1) yielding Gray Code word 101. Direct Computation: Gray Code via g(i) = i ⊕ ⌊i / 2⌋ (for i = 6) Binary Integer i = 6: 1 1 0 (Standard Binary) Right-Shifted ⌊i / 2⌋ (i ≫ 1): 0 1 1 (Shifted Right by 1) ⊕ Gray Word g(6) = 101₂: 1 0 1 Step 6 in Gray Order! ✓ XOR Rule: 0 ⊕ 0 = 0 | 1 ⊕ 1 = 0 | 0 ⊕ 1 = 1 | 1 ⊕ 0 = 1 (Outputs 1 when bits differ)
Figure 5.6: Direct $O(1)$ computation of Gray code using bitwise XOR ($\oplus$) and right shift ($\gg 1$).

Invertibility (Unranking Gray Codes): To recover index $i$ from a Gray code word $g$, start with the most significant bit (MSB) equal to $g$, and compute each subsequent bit as $i_k = i_{k+1} \mathbin{\oplus} g_k$. Because this mapping is bijective, all $2^n$ outputs are distinct.

Algebraic Proof of 1-Bit Adjacency: Suppose integer $i$ has $t$ trailing ones in binary. Incrementing $i$ to $i+1$ flips those $t$ trailing ones and the first preceding zero bit, giving $i \mathbin{\oplus} (i+1) = 2^{t+1} - 1$. Taking XOR with right shifts gives:

$$g(i) \mathbin{\oplus} g(i+1) = (2^{t+1}-1) \mathbin{\oplus} (2^t-1) = 2^t$$

Since $2^t$ contains exactly one set bit at position $t$, $g(i)$ and $g(i+1)$ differ in exactly one bit position!

Hamming Distance Comparison Across Sequences Visual chart comparing bit flips per transition between Lexicographic order and Gray Code order for 3-bit binary words. Hamming Distance Per Transition: Lexicographic vs. Gray Code Lexicographic Order (Standard Binary) Avg: 1.57 Flips • Max: 3 Flips ⚠️ 3 bit 2 bit 1 bit 1 000➔001 2 001➔010 1 010➔011 💥 3 011➔100 1 100➔101 2 101➔110 1 110➔111 Reflected Gray Code Order Avg: 1.00 Flip • Constant 1 Bit ✓ 3 bit 2 bit 1 bit 1 000➔001 1 001➔011 1 011➔010 1 010➔110 1 110➔111 1 111➔101 1 101➔100
Figure 5.7: Hamming distance graph showing how Gray code guarantees a constant Hamming distance $H(a,b) = 1$ for every transition.

Language-Independent Generator Pseudocode: Validate that $n \ge 0$. For each integer $i$ from $0$ through $2^n-1$, choose $i$ itself for lexicographic order or $i \mathbin{\oplus} \lfloor i/2 \rfloor$ for Gray order. Format the integer as an $n$-digit binary string with leading zeros and emit it.

def binary_words(n, gray=False):
    if not isinstance(n, int) or n < 0:
        raise ValueError("n must be a nonnegative integer")
    if n == 0:
        yield ""
        return
    for i in range(1 << n):
        value = i ^ (i >> 1) if gray else i
        yield format(value, f"0{n}b")


def hamming_distance(a, b):
    if len(a) != len(b):
        raise ValueError("words must have equal lengths")
    return sum(x != y for x, y in zip(a, b))


if __name__ == "__main__":
    lex = list(binary_words(3))
    gray = list(binary_words(3, gray=True))
    assert lex == ["000", "001", "010", "011",
                   "100", "101", "110", "111"]
    assert gray == ["000", "001", "011", "010",
                    "110", "111", "101", "100"]
    assert len(set(gray)) == 8
    assert set(gray) == set(lex)
    assert all(hamming_distance(a, b) == 1
               for a, b in zip(gray, gray[1:]))
    assert hamming_distance(gray[-1], gray[0]) == 1
    assert list(binary_words(0, gray=True)) == [""]
    print("Lex:", " ".join(lex))
    print("Gray:", " ".join(gray))

Complexity & Storage Analysis: Because emitting full string representations takes $O(n)$ time per word, generating all $2^n$ binary words takes $O(n 2^n)$ total time and $O(n)$ auxiliary working space. Converting the generator to an explicit array costs $O(n 2^n)$ memory.

Important Limitation: Fixed-Size Subsets

Reflected Gray codes cover all subsets across varying sizes. However, two distinct subsets of the same fixed size $k$ cannot differ by only one bit (since changing 1 bit adds or removes an element, altering $k$). A minimal-change ordering for fixed-size combinations must instead perform an element swap, changing at least 2 bits (e.g., Chase's sequence or Revolving Door algorithm).

Quick Check Exercise

Comparing Transition Costs ($n = 3$)

Question: For length $n = 3$, compare the transition from Step 3 to Step 4 in Lexicographic order versus Gray code order.

  • Lexicographic Order (Step 3 ➔ Step 4): $011 \to 100$.
    Bits changed: Left bit ($0 \to 1$), middle bit ($1 \to 0$), right bit ($1 \to 0$).
    Hamming Distance = 3 bit flips.
  • Gray Code Order (Step 3 ➔ Step 4): $010 \to 110$.
    Bits changed: Left bit ($0 \to 1$) only.
    Hamming Distance = 1 bit flip.

5.4 Backtracking and Recursive Generation

Backtracking is a systematic strategy for searching through a space of possibilities by constructing candidate objects one choice at a time. Think of it as exploring a maze: you follow a path as far as possible, and whenever you reach a dead end or a complete solution, you backtrack—stepping back to the most recent decision point to try a different path.

In a backtracking algorithm, the space of all partial solutions forms a search tree (or state-space tree):

  • The root represents the initial empty state.
  • An internal node represents a valid partial construction (a prefix).
  • A leaf node represents a fully constructed complete object (or a dead end).
  • Edges represent making a single valid decision to extend the current state.
The Backtracking Core Loop

Every recursive backtracking procedure maintains a mutable state (like a choice prefix) and follows three universal steps at each search tree node:

  1. Choose: Select a candidate symbol from the remaining available choices and append it to the current prefix.
  2. Explore: Recurse deeper into the search tree with the updated prefix. If the prefix is complete, emit it as an output object.
  3. Un-choose (Backtrack): Remove the candidate symbol from the prefix, restoring the exact state so the next candidate choice can be attempted.
The Three Stages of Backtracking State Machine Diagram showing the Choose, Explore, and Un-choose cyclic steps of a backtracking recursion node. The Three Stages of a Backtracking Decision Node 1. CHOOSE Pick Available Option Append symbol x to prefix Remove x from unused set prefix.append(x) 2. EXPLORE Recurse Deeper If len == target: Emit leaf! Else: Call backtrack(depth+1) recurse() 3. UN-CHOOSE Backtrack State Pop x from prefix stack Restore x to unused set prefix.pop()
Figure 5.8: The core state-restoration lifecycle executing at every internal search-tree node during recursive generation.

Generating Permutations via Search Trees

To generate all $n!$ permutations of $\{1, 2, \dots, n\}$, we maintain a growing prefix list and a set of unused symbols:

  • At the root node, the prefix is empty (), and all $n$ symbols are unused.
  • At depth $k$, we try appending each remaining unused symbol in turn.
  • When the unused set becomes empty (depth $n$), the current prefix is a valid leaf permutation and is emitted.
  • When trying choices at each node in increasing numeric order, the leaves are emitted in exact lexicographic order!

Correctness Invariant: The prefix contains distinct elements, and the unused set contains exactly the symbols not in the prefix. Each permutation corresponds to a unique root-to-leaf path. Because every unused symbol is systematically attempted at each node, no valid permutation is skipped, and no permutation is generated twice.

Permutation Search Tree Visualizer for n = 3 Diagram showing the full decision tree generating all 6 permutations of 1, 2, 3 in lexicographic order. Full Permutation State Space Tree for {1, 2, 3} () Root (Empty) (1) (2) (3) (1,2) (1,3) (2,1) (2,3) (3,1) (3,2) 123 132 213 231 312 321 Lexicographic Emitted Order: 123 ➔ 132 ➔ 213 ➔ 231 ➔ 312 ➔ 321 (3! = 6 Permutations)
Figure 5.9: The complete state space tree for $n = 3$. Visiting branches in numerical order generates all $n! = 6$ permutations in exact lexicographic sequence.

Pruning Search Trees: Constrained Backtracking

Backtracking becomes exceptionally powerful when combined with pruning. Instead of generating a full candidate object and checking its validity at the end, pruning checks constraint feasibility at every internal node. If a partial prefix violates a rule, the algorithm prunes (cuts off) that entire sub-tree immediately!

Consider generating binary words of length $n = 3$ with no adjacent ones (no 11 allowed):

  • At any node, we can always safely append 0.
  • We can append 1 only if the current prefix is empty or its last digit is 0.
  • If a prefix ends in 1, attempting to append 1 creates the illegal substring 11. We reject this branch immediately without exploring its children!
Pruned Backtracking Search Tree for Length-3 Words (No "11") Diagram showing valid branches expanding and invalid branch 11 being pruned immediately in crimson red. Pruned Backtracking Tree: Binary Words (n = 3, No "11") ε Root (Length 0) 0 1 00 01 10 11 ❌ PRUNED! 000 001 010 011 ❌ 100 101 5 Valid Leaves: {000, 001, 010, 100, 101} | Pruned 2 Illegal Branches Early!
Figure 5.10: Early pruning cuts off invalid branches (like 11 or 011) immediately, avoiding dead-end recursive subtree expansions.

Complexity & Performance Analysis:

  • For the "no adjacent ones" problem, the number of valid prefixes at successive lengths follows the Fibonacci recurrence $F_{n+2}$ from Chapter 4.
  • Because invalid branches are pruned in $O(1)$ time upon creation, the total number of visited internal tree nodes is strictly within a constant factor of the number of valid emitted outputs $M$.
  • Thus, internal traversal takes $O(M)$ operations for $M$ valid outputs (Constant Amortized Time per object).
  • Working memory is limited to the single mutable prefix stack and recursion call stack of depth $O(n)$.
Worked Exercise

Constrained Backtracking with Target Ones

Problem: List all binary words of length $n = 4$ containing exactly two nonadjacent ones using backtracking with pruning.

Pruning Invariants: For a prefix with $a$ ones and $s$ remaining unfilled positions, prune the branch if:

  1. $a > 2$ (Too many ones chosen).
  2. $a + s < 2$ (Not enough remaining slots to reach two ones).
  3. The last symbol chosen was 1 and we attempt to append 1 (Adjacent ones constraint).
Valid Path Emitted Word Status Reason
0 ➔ 1 ➔ 0 ➔ 1 0101 Valid Leaf 2 nonadjacent ones ✓
1 ➔ 0 ➔ 0 ➔ 1 1001 Valid Leaf 2 nonadjacent ones ✓
1 ➔ 0 ➔ 1 ➔ 0 1010 Valid Leaf 2 nonadjacent ones ✓
0 ➔ 0 ➔ 0 ➔ ... 0000 Pruned Cannot reach 2 ones ($a+s = 0+1 < 2$)
1 ➔ 1 ➔ ... 11.. Pruned Forbidden adjacent ones (11)

There are exactly 3 valid words: 0101, 1001, and 1010.
This agrees with the combinatorial choice $\binom{4 - 2 + 1}{2} = \binom{3}{2} = \mathbf{3}$!


5.5 Ranking and Unranking Combinatorial Objects

When working with a collection of $M$ combinatorial objects (such as all $n!$ permutations or all $\binom{n}{k}$ subsets), it is often necessary to map each object to a unique integer index, and vice versa. This bridge between abstract structures and simple integers is defined by two complementary operations:

  • Ranking: Converts a specific combinatorial object into its unique zero-based position index $R \in \{0, 1, \ldots, M-1\}$ within a specified ordering.
  • Unranking: Reconstructs the exact combinatorial object corresponding to a given integer index $R \in \{0, 1, \ldots, M-1\}$.
Order Defines the Rank

Rank is not an intrinsic property of an object—it depends strictly on the chosen sequence order! For binary words of length 3, the word 101 has rank 5 in standard lexicographic order ($000 \to 001 \to 010 \to 011 \to 100 \to 101$), but rank 6 in Reflected Gray Code order ($000 \to 001 \to 011 \to 010 \to 110 \to 111 \to 101$).


Permutation Ranking and the Lehmer Code

Fix the symbol set $\{0, 1, \ldots, n-1\}$ in standard lexicographic order. At each position $i$ (from left to right) of a permutation $\pi$, let $d_i$ denote the number of currently unused symbols that are strictly smaller than the symbol chosen at position $i$. Equivalently, $d_i$ is the number of smaller elements located to the right of position $i$ in the permutation.

The sequence $(d_1, d_2, \ldots, d_n)$ is called the Lehmer code of the permutation. Each digit $d_i$ satisfies $0 \le d_i \le n-i$. Furthermore, the sum of these digits equals the total inversion count of the permutation: $\operatorname{inv}(\pi) = \sum_{i=1}^n d_i$.

Using the Lehmer code, the exact lexicographic rank of permutation $\pi$ is given by:

$$\operatorname{rank}_{\mathrm{lex}}(\pi) = \sum_{i=1}^n d_i \cdot (n-i)! = d_1(n-1)! + d_2(n-2)! + \cdots + d_{n-1}1! + d_n 0!$$

Why does this formula work? At position $i$, choosing a symbol that has $d_i$ smaller available candidates skips exactly $d_i$ blocks of permutations, where each block contains $(n-i)!$ permutations sharing the same prefix. Summing all skipped blocks yields the exact number of permutations preceding $\pi$.

Lehmer Code Permutation Ranking Visualizer Diagram breaking down the conversion of permutation (2,0,3,1) into its Lehmer code digits (2,0,1,0) and calculating its lexicographic rank 13. Permutation Ranking via Factorial Base: π = (2, 0, 3, 1) Position 1: Symbol '2' Unused: {0, 1, 2, 3} Smaller unused: {0, 1} d₁ = 2 Block size: 3! = 6 2 × 3! = 12 Position 2: Symbol '0' Unused: {0, 1, 3} Smaller unused: {} d₂ = 0 Block size: 2! = 2 0 × 2! = 0 Position 3: Symbol '3' Unused: {1, 3} Smaller unused: {1} d₃ = 1 Block size: 1! = 1 1 × 1! = 1 Position 4: Symbol '1' Unused: {1} Smaller unused: {} d₄ = 0 Block size: 0! = 1 0 × 0! = 0 Lexicographic Rank = 12 + 0 + 1 + 0 = 13 (Inversions: 2 + 0 + 1 + 0 = 3)
Figure 5.11: Computing the lexicographic rank of $\pi = (2,0,3,1)$ by decomposing its Lehmer code digits $(2,0,1,0)$ using factorial weights.

The Factorial Unranking Procedure

To reverse the process and reconstruct the permutation at rank $R$ ($0 \le R < n!$), we maintain a sorted list of remaining unused symbols. At position $i$, we divide the current residual rank by $(n-i)!$:

  • The quotient gives the zero-based index of the symbol to select from the unused list.
  • The remainder becomes the new residual rank for the next position.
Unranking Permutation Step-by-Step Visualizer Diagram showing the division and remainder algorithm reconstructing permutation (2,0,3,1) from rank 13. Factorial Unranking Trace: Reconstructing Rank R = 13 (n = 4) Pos (i) Residual R Divisor (n-i)! Quotient (q) Remainder (R') Unused List Before Chosen Symbol 1 13 3! = 6 2 13 mod 6 = 1 {0, 1, 2, 3} 2 2 1 2! = 2 0 1 mod 2 = 1 {0, 1, 3} 0 3 1 1! = 1 1 1 mod 1 = 0 {1, 3} 3 4 0 0! = 1 0 0 mod 1 = 0 {1} 1
Figure 5.12: Unranking $R = 13$ for $n = 4$ produces quotients $(2, 0, 1, 0)$, uniquely recovering the original permutation $(2, 0, 3, 1)$.
Quick Practice

Permutation Rank Round-Trip ($n = 3$)

Problem: Rank $\pi = (1, 2, 0)$ and unrank $R = 3$ for $n = 3$.

  • Ranking $(1, 2, 0)$:
    1. Position 1: Symbol 1. Unused $\{0, 1, 2\}$. Smaller unused $\{0\} \implies d_1 = 1$.
    2. Position 2: Symbol 2. Unused $\{0, 2\}$. Smaller unused $\{0\} \implies d_2 = 1$.
    3. Position 3: Symbol 0. Unused $\{0\}$. Smaller unused $\{\} \implies d_3 = 0$.
    4. Rank: $1 \cdot 2! + 1 \cdot 1! + 0 \cdot 0! = 2 + 1 + 0 = \mathbf{3}$.
  • Unranking $R = 3$:
    1. $3 \div 2! = \text{quotient } \mathbf{1}$, rem $1$. From $\{0, 1, 2\}$, pick index 1 $\implies \mathbf{1}$.
    2. $1 \div 1! = \text{quotient } \mathbf{1}$, rem $0$. From $\{0, 2\}$, pick index 1 $\implies \mathbf{2}$.
    3. $0 \div 0! = \text{quotient } \mathbf{0}$, rem $0$. From $\{0\}$, pick index 0 $\implies \mathbf{0}$.
    4. Unranked Permutation: $\mathbf{(1, 2, 0)}$.

5.6 Combinatorial Number Systems

For fixed-size combinations ($k$-subsets of $\{0, 1, \dots, n-1\}$), ranking relies on binomial coefficients using colexicographic order (colex), rather than standard lexicographic order.

In colex order, two distinct $k$-subsets are compared by inspecting their largest differing element. Whichever subset contains the larger differing element comes later in colex order.

The Combinatorial Number System Theorem

Represent any $k$-subset as an increasing sorted sequence $a_1 < a_2 < \dots < a_k$. Its colex rank is given by the exact binomial sum:

$$\operatorname{rank}_{\mathrm{colex}}(a_1, a_2, \dots, a_k) = \sum_{j=1}^k \binom{a_j}{j} = \binom{a_1}{1} + \binom{a_2}{2} + \dots + \binom{a_k}{k}$$

Convention: $\binom{a}{j} = 0$ whenever $a < j$. This guarantees that the first subset $\{0, 1, \dots, k-1\}$ evaluates to rank $0$.

Colexicographic Subset Ranking Diagram Diagram showing how the subset {0, 2, 4} skips preceding subsets using Pascal binomial coefficients to compute rank 5. Colexicographic Rank Decomposition for Subset {0, 2, 4} (k = 3) j = 3: Element a₃ = 4 Subsets with max element < 4 Choices from {0, 1, 2, 3} binom(4, 3) = 4 Skips 4 earlier subsets + j = 2: Element a₂ = 2 Subsets with 2nd element < 2 Choices from {0, 1} binom(2, 2) = 1 Skips 1 earlier subset + j = 1: Element a₁ = 0 Subsets with 1st element < 0 Choices from {} binom(0, 1) = 0 Skips 0 subsets Colexicographic Rank = binom(0,1) + binom(2,2) + binom(4,3) = 0 + 1 + 4 = 5 ✓
Figure 5.13: Computing the colex rank of $\{0, 2, 4\}$ by summing the sizes of skipped blocks using binomial coefficients.

Greedy Unranking Algorithm: To find the $k$-subset matching rank $R$, search backwards from $j = k$ down to $1$. At each position $j$, find the largest integer $a_j$ such that $\binom{a_j}{j} \le R$. Subtract $\binom{a_j}{j}$ from $R$, and repeat for $j-1$ with upper bound $a_j-1$.

Colex Rank ($R$) 3-Subset $\{a_1, a_2, a_3\}$ Binomial Expansion $\binom{a_1}{1} + \binom{a_2}{2} + \binom{a_3}{3}$
0$\{0, 1, 2\}$$\binom{0}{1} + \binom{1}{2} + \binom{2}{3} = 0 + 0 + 0 = 0$
1$\{0, 1, 3\}$$\binom{0}{1} + \binom{1}{2} + \binom{3}{3} = 0 + 0 + 1 = 1$
2$\{0, 2, 3\}$$\binom{0}{1} + \binom{2}{2} + \binom{3}{3} = 0 + 1 + 1 = 2$
3$\{1, 2, 3\}$$\binom{1}{1} + \binom{2}{2} + \binom{3}{3} = 1 + 1 + 1 = 3$
4{0, 1, 4}$\binom{0}{1} + \binom{1}{2} + \binom{4}{3} = 0 + 0 + 4 = 4$
5{0, 2, 4}$\binom{0}{1} + \binom{2}{2} + \binom{4}{3} = 0 + 1 + 4 = 5$
6$\{1, 2, 4\}$$\binom{1}{1} + \binom{2}{2} + \binom{4}{3} = 1 + 1 + 4 = 6$
7$\{0, 3, 4\}$$\binom{0}{1} + \binom{3}{2} + \binom{4}{3} = 0 + 3 + 4 = 7$
8$\{1, 3, 4\}$$\binom{1}{1} + \binom{3}{2} + \binom{4}{3} = 1 + 3 + 4 = 8$
9$\{2, 3, 4\}$$\binom{2}{1} + \binom{3}{2} + \binom{4}{3} = 2 + 3 + 4 = 9$

5.7 Constant-Amortized-Time (CAT) Generation

In combinatorial generation, an algorithm is said to run in Constant Amortized Time (CAT) per object if its total internal operations to generate $M$ objects is bounded by $O(M)$. This implies that the average work per generated object is $O(1)$, even if occasional individual transition steps take significantly longer.

Consider an $n$-bit binary counter visiting all $2^n$ configurations from $000 \dots 0$ to $111 \dots 1$. To increment the counter, we scan from the rightmost bit (LSB), turning trailing 1s into 0s, until we flip the first 0 to 1.

Binary Counter Bit Flip Amortized Cascade Diagram showing the frequency of bit flips per column in a 3-bit binary counter demonstrating total 11 flips across 8 states. Bit-Flip Cascade for 3-Bit Binary Counter (n = 3) Transition Bit 3 (MSB) Bit 2 Bit 1 (LSB) Total Flips 000 ➔ 001 ——Flip (1) 1 flip 001 ➔ 010 —Flip (1)Flip (0) 2 flips 010 ➔ 011 ——Flip (1) 1 flip 011 ➔ 100 Flip (1)Flip (0)Flip (0) 💥 3 flips 100 ➔ 101 ——Flip (1) 1 flip 101 ➔ 110 —Flip (1)Flip (0) 2 flips 110 ➔ 111 ——Flip (1) 1 flip Column Sums: 1 flip 3 flips 7 flips Total = 11 Bit Flips ✓
Figure 5.14: Total bit flips across 7 transitions for $n=3$. Position $j$ flips $2^{3-j+1}-1$ times, yielding $7 + 3 + 1 = 11$ total flips ($1.375$ flips/state).

Proof of Constant Amortized Complexity: Numbering bits from right to left as $j = 1, 2, \dots, n$, bit position $j$ flips exactly $2^{n-j+1} - 1$ times across the full counting sequence. Summing across all $n$ positions:

$$\text{Total Bit Flips} = \sum_{j=1}^n (2^{n-j+1} - 1) = 2^{n+1} - n - 2 < 2 \cdot 2^n$$

Because the total work across all $2^n$ outputs is strictly less than $2 \cdot 2^n$, the average internal work per state transition is less than 2 bit flips—proving $O(1)$ constant amortized time!


5.8 Uniform Random Sampling of Finite Structures

Uniform sampling selects an object from a finite set of size $M$ such that every object has an identical probability $p = 1/M$ of being chosen.

1. Sampling via Unranking: Draw a random integer $R$ uniformly from $\{0, 1, \dots, M-1\}$, then return $\operatorname{unrank}(R)$. Because unranking is a bijection, every object corresponds to exactly one rank, guaranteeing exact uniform probability $1/M$ in $O(1)$ sampling time without generating preceding objects!

2. Exact Rejection Sampling (Avoiding Modulo Bias): Naively computing random_int() % M introduces probability bias if the random generator range is not a perfect multiple of $M$. To sample uniformly without bias, select the smallest power of two $2^b \ge M$, generate a $b$-bit integer, and reject any value $\ge M$, repeating until a valid integer $< M$ is accepted.

Modulo Bias vs Exact Rejection Sampling Visualizer Diagram showing how modulo 3 over 8 items creates unfair probabilities 3/8, 3/8, 2/8 whereas rejection sampling restores equal 1/3 odds. Eliminating Modulo Bias via Exact Rejection Sampling (M = 3) ❌ Naive Modulo: rand(0..7) % 3 Residue 0 P = 3/8 (37.5%) Residue 1 P = 3/8 (37.5%) Residue 2 P = 2/8 (25.0%) Unfair distribution! Residue 2 is undersampled. ✓ Exact Rejection: 2-bit rand(), Reject ≥ 3 Value 0 P = 1/3 Value 1 P = 1/3 Value 2 P = 1/3 Value 3 REJECT! Perfect uniform probability 1/3 for accepted items!
Figure 5.15: Naive modulo division introduces sampling bias, whereas rejection sampling guarantees mathematically exact uniform probabilities.

Direct Permutation Sampling: The Fisher–Yates Shuffle

Rather than converting huge ranks, the Fisher–Yates shuffle generates a uniformly random permutation in $O(n)$ time using in-place swaps:

  1. Start with an array initialized to sorted order $(0, 1, \dots, n-1)$.
  2. For index $i$ running from $n-1$ down to $1$:
    • Pick a uniform random integer $j$ from $\{0, 1, \dots, i\}$.
    • Swap elements at position $i$ and position $j$.

Proof of Uniformity: At step $i$, position $i$ receives a uniformly chosen element from the $i+1$ remaining candidates with probability $1/(i+1)$. Multiplying probabilities across all steps gives:

$$P(\pi) = \frac{1}{n} \cdot \frac{1}{n-1} \cdots \frac{1}{2} \cdot \frac{1}{1} = \frac{1}{n!}$$
Fisher-Yates In-Place Shuffle Trace Diagram showing step by step array swaps of Fisher Yates shuffle on 4 items. Fisher–Yates In-Place Shuffle Step-by-Step (n = 4) Init Array: 0 1 2 3 Sorted starting state i = 3: Pick j ∈ {0..3} (Say j=1): 0 3 2 1 Pos 3 Fixed: '1' ✓ i = 2: Pick j ∈ {0..2} (Say j=0): 2 3 0 1 Pos 2 Fixed: '0' ✓ i = 1: Pick j ∈ {0..1} (Say j=1): 2 3 0 1 Result: Permutation (2, 3, 0, 1) ✓
Figure 5.16: Fisher–Yates shuffle fixes one random element per step, generating a perfectly uniform random permutation in $O(n)$ time.
Quick Practice

Uniform Statistic Distribution Practice

Question: In a uniform permutation of 3 symbols, is the inversion count distribution uniform across $\{0, 1, 2, 3\}$?

  • Total Permutations ($3! = 6$):
    • $\operatorname{inv}(0, 1, 2) = \mathbf{0}$ (1 permutation) $\implies P(0) = \mathbf{1/6}$.
    • $\operatorname{inv}(0, 2, 1) = \mathbf{1}$, $\operatorname{inv}(1, 0, 2) = \mathbf{1}$ (2 permutations) $\implies P(1) = \mathbf{2/6}$.
    • $\operatorname{inv}(1, 2, 0) = \mathbf{2}$, $\operatorname{inv}(2, 0, 1) = \mathbf{2}$ (2 permutations) $\implies P(2) = \mathbf{2/6}$.
    • $\operatorname{inv}(2, 1, 0) = \mathbf{3}$ (1 permutation) $\implies P(3) = \mathbf{1/6}$.
  • Answer: No! The probabilities are $(1/6, 2/6, 2/6, 1/6)$. Sampling permutations uniformly does not imply a uniform distribution on their derived statistics.

Build and verify a rank–unrank round trip

Generation provides an independent list against which a ranking system can be checked. Ranking supplies an exact index, and unranking supplies direct access to an object. Once the two maps are proved inverse, a uniform random index becomes a uniform random object.

Permutation inversion counts add another useful consistency check. The Lehmer digits used to compute a rank must sum to the inversion count obtained by testing position pairs. This connects permutation generation, ranking, and sampling without assuming that inversion counts themselves are uniformly distributed.

One application is testing a scheduler on all small orderings of distinct tasks, then sampling larger orderings when exhaustive testing becomes impractical. A stored rank identifies the chosen ordering exactly under a fixed symbol order and ranking convention. Uniform sampling covers permutations fairly, but it does not guarantee equal coverage of properties such as inversion count, execution time, or resource demand.

Investigation

Build and verify a rank–unrank round trip

Inputs and conventions. Use permutations of $\{0,1,2,3\}$ in lexicographic order, and three-element subsets of $\{0,1,2,3,4\}$ in colex order. All ranks are zero-based. Use exact integers and validate input ranges.

Task 1: warm-up calculations. Generate the six permutations of $\{0,1,2\}$ in lexicographic order. Compute their Lehmer codes, ranks, and inversion counts. Separately, reproduce the ten-entry colex table in Section 5.6.

Solution check: The permutation ranks are $0,1,2,3,4,5$, and the corresponding inversion counts are $0,1,1,2,2,3$. The colex ranks are $0$ through $9$ in the displayed subset order.

Task 2: implement both directions. Implement factorial ranking and unranking for permutations. Implement binomial-sum ranking and greedy unranking for combinations. Use the procedures in Sections 5.5 and 5.6, including explicit handling of empty objects.

Worked checks: Permutation $(2,0,3,1)$ has rank $13$ and three inversions. Subset $\{0,2,4\}$ has colex rank $5$. Unranking those integers must recover those objects.

Task 3: exhaustive verification. Generate all $24$ four-symbol permutations independently using next-permutation or backtracking. For each emitted permutation, check that its rank equals its position in the generated lexicographic list. Check both round trips for every permutation and every integer rank from zero through $23$. Compare each Lehmer digit sum with a direct inversion counter.

Generate the ten three-element subsets independently. Sort them by their tuples read from largest element to smallest to obtain colex order. Check that their ranks are exactly zero through nine, in that order, and check both round trips for every subset and every rank.

Testing only a round trip is insufficient to verify a specified order: two mutually inverse maps could still implement a different order. The independently generated reference lists provide that additional check.

Task 4: prove the general round trip. For permutations, explain why each quotient chooses one factorial-sized prefix block and each remainder stays within it. For combinations, establish the residual bound after each greedy binomial choice using Pascal’s identity. Finish both arguments with their empty-object base cases.

Hints: Induct on the number of positions still to fill. State both inverse identities, rather than proving only that unranking produces a legal object. For a combination step, the residual must be smaller than $\binom{a_j}{j-1}$ after selecting $a_j$.

Task 5: connect to sampling. Draw a uniform integer from zero through $23$ and unrank it as a permutation. Explain why each permutation has probability $1/24$. Collect a frequency table if desired, but distinguish a statistical check from the bijection proof: observed frequencies need not be exactly equal in a finite experiment.

Boundary checks. Test the empty permutation and the empty subset, each with rank zero; the first and last ranks; a repeated symbol in a purported permutation; and an out-of-range rank. Reject invalid inputs rather than silently reducing ranks modulo the family size.

Optional extension. Use completion counts to rank and unrank length-five binary words with no adjacent ones in lexicographic order. At each position, the block beginning with $0$ has as many words as its valid completions. Ranking skips that block when the actual next symbol is $1$; unranking enters it when the residual rank is smaller than its size. There are $13$ words, so the valid ranks are zero through twelve. Prove the round trip, then obtain a uniform constrained word by choosing one of those ranks uniformly.

Deliverables. Submit the reference lists, implementations, exhaustive-check results, proofs of both inverse identities, and the sampling argument. Report the order and indexing convention beside every rank.

Assessment: 25 points. Award five points each for correct generation and ordering; permutation ranking with inversion checks; combination ranking with correct colex conventions; proofs and boundary handling; and a sampling argument that separates mathematical guarantees from experimental evidence.

Next in the Series

In Chapter 6: Binomial Coefficients and Identities, we'll explore See algebraic identities as counts of the same objects.