The O(n) Club: Permutation Sequence: The Algorithmic VIP Pass

The O(n) Club: Permutation Sequence — The Algorithmic VIP Pass

⚡ TL;DR

Find the k-th permutation (in lex order) of numbers 1 to n… without generating all n! options, because only Indiana Jones loves that kind of adventure.
Brute force: Don’t. It’s O(n!) and your machine is not made of infinite RAM.
Fast way: “Factorial number system” magic. Jump to the permutation you want, like a time traveler who knows the seating chart.

// Brute-force (pro-level bad idea)
// int bruteForce(int n, int k) {
//     List<String> perms = allPermutations(n);
//     Collections.sort(perms);
//     return perms.get(k - 1);
// }

🧠 How Devs Usually Mess This Up

Classic blunder: use next_permutation or recursion to dump all possible permutations into a list, then grab the k-th one. When n hits 9, this approach melts CPUs faster than you can say “heap overflow.” The other top mistake? Classic off-by-one errors—k is 1-based, but every code sample is 0-based. You’ll debug this at 2AM and question your life choices.

🔍 What’s Actually Going On

Here’s the plot: Imagine all n! permutations, sorted. Each choice for your first digit “owns” a block of (n-1)! consecutive permutations. So you pick the right block by dividing (k-1) by (n-1)!. Then, for the next position, repeat with the updated k on the remaining digits. This is called the factorial number system—think of it as an address, but in factorial steps instead of decimal places. You’re not walking the block, you’re riding a zipline to your answer.

🛠️ PseudoCode

  • Prep the Pool:
    • Create a list with numbers 1 to n.
    // List<Integer> pool = [1,2...n]
  • Kick off with Zero-Based Math:
    • k = k – 1, because computers.
    k = k - 1;
  • For each position:
    • block size = factorial(pool.size() – 1)
    • index = k / block size
    • append pool.get(index) to result, remove from pool
    • k = k % block size and keep zipping through the list
    for (int i = n; i > 0; i--) {
        blockSize = fact(i-1);
        index = k / blockSize;
        result += pool.get(index);
        pool.remove(index);
        k = k % blockSize;
    }

💻 The Code

public String getPermutation(int n, int k) {
    List<Integer> pool = new ArrayList<>();
    for (int i = 1; i <= n; i++) pool.add(i);
    int[] factorial = new int[n+1];
    factorial[0] = 1;
    for (int i = 1; i <= n; i++) factorial[i] = factorial[i-1] * i;
    k--;
    StringBuilder sb = new StringBuilder();
    for (int i = n; i > 0; i--) {
        int block = factorial[i-1];
        int idx = k / block;
        sb.append(pool.remove(idx));
        k = k % block;
    }
    return sb.toString();
}

⚠️ Pitfalls, Traps & Runtime Smacks

  • This problem is an off-by-one buffet: Don’t forget k--, unless you like subtle bugs.
  • Update k after EVERY step: k = k % block. If you don’t, you’ll end up with a shuffle worthy of bad Spotify playlists.
  • LinkedList vs ArrayList: Removal is O(n) and fine for n ≤ 9, but don’t overthink it.
  • Time: O(n2) (yes, the remove hurts, but not much for small n)
  • Space: O(n), so your machine won’t explode.

🧠 Interviewer Brain-Tattoo

“If it feels like O(n!), your answer and their patience both expire in factorial time.”

Previous Article

The O(n) Club: Maximum Width of Binary Tree: Counting Gaps Like a Pro

Next Article

The O(n) Club: All Possible Parentheses — Recursion Goes Wild