Recursion & Backtracking · Recursion & Backtracking Fundamentals

Kth Permutation by Factorial Counting

Computing the kth permutation directly by figuring out, digit by digit, which remaining element the target rank must start with - using factorials to count how many permutations begin with each candidate, without ever generating the other permutations.

  • 5 min read
  • 8 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: from permutation back to rank
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

The permutations of {1, ..., n} in lexicographic order form a list of n! entries: 362,880 for n = 9, and 2.4 × 10^18 for n = 20. Generating them all to find the k-th one is hopeless beyond tiny n. The list has a rigid structure, though. It's grouped by first digit, then by second digit within each group, and every block has a known size. So the k-th permutation can be computed one digit at a time, and a permutation's rank can be computed the same way in reverse.

We'll go from simple to more technical:

  1. The pattern: divide by block sizes, (n - 1)!, (n - 2)!, ..., to pick each digit.
  2. A second flavor: the reverse, from a permutation to its rank.
  3. Where ranking and unranking are used (sampling, indexing orderings).
  4. What breaks it: off-by-one, overflow, and counting digits already used.

The approach

Java

String getPermutation(int n, int k) {
    List<Integer> digits = new ArrayList<>();          // unused digits, in order
    long[] factorial = new long[n + 1];
    factorial[0] = 1;
    for (int i = 1; i <= n; i++) { digits.add(i); factorial[i] = factorial[i - 1] * i; }
    k--;                                                // 1-indexed rank -> 0-indexed
    StringBuilder result = new StringBuilder();
    for (int position = n; position >= 1; position--) {
        long block = factorial[position - 1];          // permutations sharing the next digit
        int index = (int) (k / block);                  // which remaining digit comes next
        k %= block;                                     // rank within that block
        result.append(digits.remove(index));
    }
    return result.toString();
}

Python

def get_permutation(n: int, k: int) -> str:
    digits = list(range(1, n + 1))                     # unused digits, in order
    factorial = [1] * (n + 1)
    for i in range(1, n + 1):
        factorial[i] = factorial[i - 1] * i
    k -= 1                                             # 1-indexed rank -> 0-indexed
    result = []
    for position in range(n, 0, -1):
        block = factorial[position - 1]                # permutations sharing the next digit
        index, k = divmod(k, block)                    # which remaining digit comes next, and the rank within its block
        result.append(str(digits.pop(index)))          # pop by POSITION - list.remove() is by value
    return "".join(result)

Fixing the first digit leaves (n - 1)! ways to arrange the rest, so the first (n - 1)! permutations start with the smallest digit, the next (n - 1)! with the second smallest, and so on. Dividing the 0-indexed rank by (n - 1)! picks the first digit; the remainder is the rank inside that block - the same problem with one digit fewer. For n = 4, k = 9: k - 1 = 8, 8 / 3! = 1, so the first digit is 2 (the second smallest), and the answer is 2314.

A second flavor: from permutation back to rank

The same blocks run backward. Every digit that is smaller than perm[i] and still unused could have started a whole earlier block of (n - 1 - i)! permutations:

Java

long rank = 0;
for (int i = 0; i < n; i++) {
    int smallerUnused = 0;
    for (int j = i + 1; j < n; j++) if (perm[j] < perm[i]) smallerUnused++;   // still available at position i
    rank += smallerUnused * factorial[n - 1 - i];
}
return rank + 1;                                        // back to 1-indexed

Python

from math import factorial


def permutation_rank(perm: list[int]) -> int:
    n, rank = len(perm), 0
    for i in range(n):
        smaller_unused = sum(1 for j in range(i + 1, n) if perm[j] < perm[i])   # still available at position i
        rank += smaller_unused * factorial(n - 1 - i)
    return rank + 1                                    # back to 1-indexed

For 3 5 1 4 2: 2 * 4! + 3 * 3! + 0 + 1 * 1! + 0 = 67, so it's the 68th permutation. Counting all smaller digits (perm[i] - 1) instead of the smaller unused ones overcounts, since digits already placed can't start a block - it gives 77.

Where you'll see it

Converting between a permutation and its rank (the factorial number system, or Lehmer code) lets a permutation be stored as one integer, lets test harnesses reproduce "the k-th ordering" from a seed, and lets search algorithms index permutation states compactly - for example, a table indexed by the rank of a puzzle's arrangement.

Which tool for which job?

You needUse
The k-th permutationk--, then pick each digit by dividing by the block size
The rank of a permutationadd (smaller unused digits) × (block size) at each position
Both, for large na Fenwick tree over the unused digits: O(n log n)
Repeated elementsblock sizes divided by the factorials of the multiplicities
A uniformly random permutationFisher-Yates shuffle
n above 20 in JavaBigInteger ranks

Common mistakes

MistakeWhat happensDo this instead
Skipping k--n = 3, k = 3 gives 231 instead of 213work 0-indexed
Factorials in int13! comes out as 1932053504long up to 20!, BigInteger beyond
Counting all smaller digits for the rank77 instead of 67 for 3 5 1 4 2count only the unused ones
digits.remove(index) in Pythonremoves by value, not positiondigits.pop(index)

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. For n = 4, how many permutations start with each digit, and what's the first digit of the 9th?

Show answer

3! = 6 each. Ranks 1-6 start with 1 and 7-12 start with 2, so the 9th starts with 2. In code: (9 - 1) / 6 = 1, the second smallest remaining digit.

2. What goes wrong if k isn't decremented first?

Show answer

The division treats k as 0-indexed, so every answer shifts one permutation later: n = 3, k = 3 gives 231 instead of 213. For k = n!, the first division returns n, one past the last remaining digit.

3. What is the rank of 3 5 1 4 2, and why count only unused smaller digits?

Show answer
  1. At each position, a smaller digit that's still available could have started an earlier block: 2 * 4! (for 1 and 2 before 3) + 3 * 3! + 0 + 1 * 1! + 0 = 67 earlier permutations. Digits already placed can't start a block at a later position, so counting them (perm[i] - 1) overcounts - to 77.

Key takeaways

  • Each choice of the next digit owns a block of (remaining - 1)! consecutive permutations.
  • Unranking: convert k to 0-indexed, divide by the block size to pick the digit, keep the remainder.
  • Ranking runs it backward: sum (smaller unused digits) * block size, then add 1.
  • Use long factorials - fine up to n = 20; beyond that, BigInteger.
  • O(n^2) with a list (O(n log n) with a Fenwick tree) instead of O(n!).

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
k-th permutationk--, divide by block sizes
Ranksum of (smaller unused) × block size
Large nFenwick tree, O(n log n)
Repeated elementsmultinomial block sizes
A random permutationFisher-Yates

Snippets worth knowing by heart

Java

List<Integer> digits = new ArrayList<>();
for (int d = 1; d <= n; d++) digits.add(d);
k--;                                                  // 0-indexed
StringBuilder sb = new StringBuilder();
for (int i = n; i >= 1; i--) {
    long block = fact[i - 1];
    int idx = (int) (k / block);
    k %= block;
    sb.append(digits.remove(idx));                    // by position
}

Python

def rank(perm):
    n, r = len(perm), 0
    for i, x in enumerate(perm):
        smaller_unused = sum(1 for y in perm[i + 1:] if y < x)
        r += smaller_unused * math.factorial(n - 1 - i)
    return r + 1

The traps

  • No k--: n = 3, k = 3 gives 231 instead of 213. Convert k to 0-indexed first.
  • int factorials: 13! overflows. Use long up to 20!, BigInteger beyond.
  • Counting used digits in the rank: the rank comes out too high. Count only smaller digits not yet used.
  • list.remove(i) in Python removes by value, not position. Use digits.pop(i).

Staff-level points, one line each

  • The rank minus one, in factoradic, is the list of "smaller unused" counts.
  • Fenwick ranking matched O(n²) ranking on 50 permutations; the rank of a random 1,000-permutation has 2,567 digits.
  • 13! as an int was 1932053504; 21! as a long was -4249290049419214848. multiplyExact throws instead.
  • Multiset block sizes matched brute force on [0, 0, 0, 1, 1, 2, 3] (420 permutations).
  • random.shuffle can't reach most permutations from n = 2,081 (n! > 2^19937).

Say it in an interview

  • "Each first digit owns a block of (n - 1)! permutations, so I divide to pick it."
  • "I subtract 1 first, because the block arithmetic is 0-indexed."
  • "Ranking adds the smaller unused digits times the block sizes."
  • "For big n, I'd use a Fenwick tree and big integers."

Practice

This skill comes with 8 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Kth Permutation by Factorial Counting free

Permutation Rank

MediumProCoding problemJava · Python

The n! permutations of {1, 2, ..., n} are listed in lexicographic order. Given one permutation, return its 1-indexed position in that list - the inverse of the Permutation Sequence problem.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Permutation Sequence

HardProCoding problemJava · Python

Write out every ordering of the digits 1 to n in dictionary order, and return the k-th one (counting from 1) as a string.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Kth Permutation by Factorial Counting free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime