Problem
Given n and k, find the k-th lexicographic permutation of the sequence [1, 2, 3, ..., n]. For example, [1, 2, 3] produces six permutations in order: "123", "132", "213", "231", "312", "321".
- Input: n = 3, k = 3
- Output: "213"
- Explanation: The third permutation of [1, 2, 3] in sorted order is "213".
Counter-example: if n = 3 and k = 6, the answer is "321" โ the last permutation โ showing that k can reach all n! entries.
Intuition
Every group of permutations sharing the same first digit has exactly (n-1)! members. So integer-dividing k by (n-1)! tells us which digit goes first, without listing a single permutation. After placing that digit, the problem reduces to finding the k-remainder-th permutation of the remaining n-1 digits โ and the same logic applies again at each position.
Approach 1 โ Generate All Permutations
Build every permutation in lexicographic order by backtracking through digits in sorted sequence, then return the one at index k-1.
- Construct a sorted list of digit strings ["1", "2", ..., "n"]
- Recursively pick each unused digit in order, appending it to a running sequence
- When the sequence reaches length n, record it as a complete permutation
- After all n! permutations are collected, return the entry at index k-1
1def getPermutation(n: int, k: int) -> str:
2 all_perms = []
3
4 def backtrack(remaining: list, current: list):
5 if not remaining:
6 all_perms.append(''.join(current))
7 return
8 for i in range(len(remaining)):
9 current.append(remaining[i])
10 backtrack(remaining[:i] + remaining[i + 1:], current)
11 current.pop()
12
13 backtrack([str(i) for i in range(1, n + 1)], [])
14 return all_perms[k - 1]Time: O(n! ร n) โ generating n! permutations each of length n
Space: O(n! ร n) โ every permutation is stored in memory
Approach 2 โ Factorial Number System
Directly compute each digit using division rather than listing anything. Decrement k to 0-indexed first, then at each position divide k by the group size (position-1)! to find which remaining digit belongs there.
- Build
available_digits= [1, 2, ..., n] and precomputefactorials[0..n] - Decrement k by 1 to make it 0-indexed
- For each position from n down to 1:
group_size = factorials[position - 1]โ permutations sharing the same leading digitdigit_index = k // group_sizeโ which digit from the remaining list comes next- Append
available_digits[digit_index]to result and remove it from the list - Update
k = k % group_sizeโ offset within the chosen group
- Return the assembled result string
1def getPermutation(n: int, k: int) -> str:
2 import math
3 available_digits = [str(i) for i in range(1, n + 1)]
4 k -= 1 # switch to 0-indexed so integer division is exact at boundaries
5
6 result = []
7 for position in range(n, 0, -1):
8 group_size = math.factorial(position - 1) # perms per leading digit
9 digit_index = k // group_size
10 result.append(available_digits[digit_index])
11 available_digits.pop(digit_index)
12 k %= group_size # offset within the group just chosen
13 return ''.join(result)Time: O(nยฒ) โ n positions, each costing O(n) for the list removal
Space: O(n) โ the available digits list and factorial array
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Generate All Permutations | O(n! ร n) | O(n! ร n) | Only for very small n (โค 5) where code brevity outweighs cost |
| Factorial Number System | O(nยฒ) | O(n) | Always โ the only practical approach for n up to 9 |
Common Mistakes
- Not converting k to 0-indexed before the loop โ the formula
k // group_sizeassumes 0-indexed k; skippingk -= 1at the start makes the very first digit selection one step too far, cascading errors into every subsequent position - Using
factorial(position)instead offactorial(position - 1)as the group size โ each leading digit governs exactly (position-1)! permutations, not position!; using the wrong factorial shifts every index by a factor - Forgetting to update k with modulo โ omitting
k %= group_sizemeans subsequent positions see the full original offset rather than the remainder within the chosen group, picking wrong digits from position 2 onward - Not removing the chosen digit from the available list โ after selecting
available_digits[digit_index], that digit must be deleted so later positions draw only from the remaining pool; skipping this allows digits to appear twice - In the brute force, not iterating digits in ascending order โ backtracking must always add digits smallest-first (1 โ 2 โ ... โ n) so permutations accumulate in lexicographic order; any other traversal puts the wrong permutation at index k-1
Related Problems
permutationsโ generates all permutations rather than targeting one specific indexnext-permutationโ advances a given permutation to the next one in lexicographic ordercombinationsโ enumerates subsets of size k using a comparable counting structurepalindrome-partitioningโ backtracking over all valid arrangements of a stringsubsetsโ builds every subset with the same backtracking pattern used in the brute force