Permutation Sequence (Kth)
Imagine you have a list of tasks to perform in every possible order, but you want to quickly jump to the k-th order without enumerating all permutations.
Given n and k, return the k-th permutation sequence of the numbers [1, 2, ..., n] in lexicographical order. Input: two integers n and k. Output: a string representing the k-th permutation.
1 ≤ n ≤ 91 ≤ k ≤ n!"n = 3, k = 3""213"The permutations of [1,2,3] in order are: 123, 132, 213, 231, 312, 321. The 3rd is '213'.
- n = 1, k = 1 → output '1' (smallest input)
- k = 1 → output is the first permutation in ascending order
- k = n! → output is the last permutation in descending order
- n = 9, k = 362880 (9!) → largest input within constraints
Incorrect index calculation leads to wrong permutation
✅ Always do k = k - 1 before calculations
Repeated numbers appear in the result
✅ Remove the selected number from the available list after choosing
Wrong index calculation or inefficient repeated factorial computation
✅ Precompute factorials once before main loop
Solution times out or crashes due to memory/time limits
✅ Use direct computation approach instead of brute force
Incorrect output or runtime errors
✅ Test and handle boundary cases explicitly if needed
Intuition
Generate all permutations of [1..n] in lex order using backtracking, then return the k-th one.
Algorithm
- Initialize a list to store all permutations.
- Use backtracking to generate all permutations of [1..n].
- Stop when the k-th permutation is generated.
- Return the k-th permutation as a string.
def getPermutation(n, k):
res = []
used = [False] * n
path = []
def backtrack():
if len(path) == n:
res.append(''.join(map(str, path)))
return
for i in range(n):
if not used[i]:
used[i] = True
path.append(i + 1)
backtrack()
path.pop()
used[i] = False
backtrack()
return res[k - 1]
# Driver code
if __name__ == '__main__':
print(getPermutation(3, 3)) # Output: '213'res = []Stores all permutations generated so farused = [False] * nTracks which numbers are already in the current permutation pathdef backtrack():Defines the recursive function to build permutationsif len(path) == n:Base case: a full permutation is formedres.append(''.join(map(str, path)))Add the current permutation as a string to resultsfor i in range(n):Try each number from 1 to n if not usedused[i] = TrueMark number as used before recursionpath.append(i + 1)Add number to current permutation pathbacktrack()Recurse to build deeper permutationspath.pop()Backtrack by removing last numberused[i] = FalseMark number as unused for other branchesreturn res[k - 1]Return the k-th permutation from the listimport java.util.*;
public class Solution {
List<String> res = new ArrayList<>();
boolean[] used;
int n;
public String getPermutation(int n, int k) {
this.n = n;
used = new boolean[n];
backtrack(new StringBuilder());
return res.get(k - 1);
}
private void backtrack(StringBuilder path) {
if (path.length() == n) {
res.add(path.toString());
return;
}
for (int i = 0; i < n; i++) {
if (!used[i]) {
used[i] = true;
path.append(i + 1);
backtrack(path);
path.deleteCharAt(path.length() - 1);
used[i] = false;
}
}
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.getPermutation(3, 3)); // Output: 213
}
}List<String> res = new ArrayList<>()Stores all permutations generatedboolean[] usedTracks which numbers are used in current pathbacktrack(new StringBuilder())Starts recursion with empty pathif (path.length() == n)Base case: full permutation formedres.add(path.toString())Add current permutation to resultsfor (int i = 0; i < n; i++)Try each number from 1 to nused[i] = trueMark number as used before recursionpath.append(i + 1)Add number to current pathbacktrack(path)Recurse deeperpath.deleteCharAt(path.length() - 1)Backtrack by removing last numberused[i] = falseMark number as unused for other branchesreturn res.get(k - 1)Return the k-th permutation#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Solution {
public:
vector<string> res;
vector<bool> used;
int n;
void backtrack(string &path) {
if ((int)path.size() == n) {
res.push_back(path);
return;
}
for (int i = 0; i < n; i++) {
if (!used[i]) {
used[i] = true;
path.push_back('1' + i);
backtrack(path);
path.pop_back();
used[i] = false;
}
}
}
string getPermutation(int n, int k) {
this->n = n;
used.assign(n, false);
string path = "";
backtrack(path);
return res[k - 1];
}
};
int main() {
Solution sol;
cout << sol.getPermutation(3, 3) << endl; // Output: 213
return 0;
}vector<string> resStores all generated permutationsvector<bool> usedTracks usage of numbers in current permutationvoid backtrack(string &path)Recursive function to build permutationsif ((int)path.size() == n)Base case: full permutation formedres.push_back(path)Add current permutation to resultsfor (int i = 0; i < n; i++)Try each number from 1 to nused[i] = trueMark number as used before recursionpath.push_back('1' + i)Add number to current pathbacktrack(path)Recurse deeperpath.pop_back()Backtrack by removing last numberused[i] = falseMark number as unused for other branchesreturn res[k - 1]Return the k-th permutationfunction getPermutation(n, k) {
const res = [];
const used = new Array(n).fill(false);
const path = [];
function backtrack() {
if (path.length === n) {
res.push(path.join(''));
return;
}
for (let i = 0; i < n; i++) {
if (!used[i]) {
used[i] = true;
path.push(i + 1);
backtrack();
path.pop();
used[i] = false;
}
}
}
backtrack();
return res[k - 1];
}
// Test
console.log(getPermutation(3, 3)); // Output: 213const res = []Stores all permutations generatedconst used = new Array(n).fill(false)Tracks which numbers are used in current pathfunction backtrack()Recursive function to build permutationsif (path.length === n)Base case: full permutation formedres.push(path.join(''))Add current permutation as string to resultsfor (let i = 0; i < n; i++)Try each number from 1 to nused[i] = trueMark number as used before recursionpath.push(i + 1)Add number to current pathbacktrack()Recurse deeperpath.pop()Backtrack by removing last numberused[i] = falseMark number as unused for other branchesreturn res[k - 1]Return the k-th permutationO(n! * n)O(n! * n)Generating all permutations takes n! time and each permutation is length n, so total O(n! * n).
This approach times out for larger n because it generates all permutations instead of directly computing the k-th.
Intuition
Generate permutations in lex order, but stop recursion as soon as the k-th permutation is reached.
Algorithm
- Initialize a global counter to track how many permutations have been generated.
- Use backtracking to generate permutations in lex order.
- When the counter reaches k, record the current permutation and stop recursion.
- Return the recorded permutation.
def getPermutation(n, k):
used = [False] * n
path = []
count = [0]
result = ['']
def backtrack():
if len(path) == n:
count[0] += 1
if count[0] == k:
result[0] = ''.join(map(str, path))
return
if result[0]:
return
for i in range(n):
if not used[i]:
used[i] = True
path.append(i + 1)
backtrack()
path.pop()
used[i] = False
backtrack()
return result[0]
# Driver code
if __name__ == '__main__':
print(getPermutation(3, 3)) # Output: '213'count = [0]Tracks how many permutations have been generated so farresult = ['']Stores the k-th permutation once foundif len(path) == n:Base case: full permutation formedcount[0] += 1Increment count when a permutation is completedif count[0] == k:Check if current permutation is the k-thresult[0] = ''.join(map(str, path))Save the k-th permutationif result[0]:Stop recursion early if k-th permutation foundfor i in range(n):Try each number from 1 to nused[i] = TrueMark number as usedpath.append(i + 1)Add number to current pathbacktrack()Recurse deeperpath.pop()Backtrack by removing last numberused[i] = FalseMark number as unusedimport java.util.*;
public class Solution {
List<String> res = new ArrayList<>();
boolean[] used;
int n, count = 0;
String result = "";
public String getPermutation(int n, int k) {
this.n = n;
used = new boolean[n];
backtrack(new StringBuilder(), k);
return result;
}
private void backtrack(StringBuilder path, int k) {
if (path.length() == n) {
count++;
if (count == k) {
result = path.toString();
}
return;
}
if (!result.isEmpty()) return;
for (int i = 0; i < n; i++) {
if (!used[i]) {
used[i] = true;
path.append(i + 1);
backtrack(path, k);
path.deleteCharAt(path.length() - 1);
used[i] = false;
}
}
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.getPermutation(3, 3)); // Output: 213
}
}int count = 0Tracks how many permutations generatedString result = ""Stores the k-th permutation once foundif (path.length() == n)Base case: full permutation formedcount++Increment count when a permutation is completedif (count == k)Check if current permutation is the k-thresult = path.toString()Save the k-th permutationif (!result.isEmpty()) returnStop recursion early if k-th permutation foundfor (int i = 0; i < n; i++)Try each number from 1 to nused[i] = trueMark number as usedpath.append(i + 1)Add number to current pathbacktrack(path, k)Recurse deeperpath.deleteCharAt(path.length() - 1)Backtrack by removing last numberused[i] = falseMark number as unused#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Solution {
public:
vector<bool> used;
int n, count = 0;
string result = "";
void backtrack(string &path, int k) {
if ((int)path.size() == n) {
count++;
if (count == k) {
result = path;
}
return;
}
if (!result.empty()) return;
for (int i = 0; i < n; i++) {
if (!used[i]) {
used[i] = true;
path.push_back('1' + i);
backtrack(path, k);
path.pop_back();
used[i] = false;
}
}
}
string getPermutation(int n, int k) {
this->n = n;
used.assign(n, false);
string path = "";
backtrack(path, k);
return result;
}
};
int main() {
Solution sol;
cout << sol.getPermutation(3, 3) << endl; // Output: 213
return 0;
}int count = 0Tracks how many permutations generatedstring result = ""Stores the k-th permutation once foundif ((int)path.size() == n)Base case: full permutation formedcount++Increment count when a permutation is completedif (count == k)Check if current permutation is the k-thresult = pathSave the k-th permutationif (!result.empty()) returnStop recursion early if k-th permutation foundfor (int i = 0; i < n; i++)Try each number from 1 to nused[i] = trueMark number as usedpath.push_back('1' + i)Add number to current pathbacktrack(path, k)Recurse deeperpath.pop_back()Backtrack by removing last numberused[i] = falseMark number as unusedfunction getPermutation(n, k) {
const used = new Array(n).fill(false);
const path = [];
let count = 0;
let result = '';
function backtrack() {
if (path.length === n) {
count++;
if (count === k) {
result = path.join('');
}
return;
}
if (result) return;
for (let i = 0; i < n; i++) {
if (!used[i]) {
used[i] = true;
path.push(i + 1);
backtrack();
path.pop();
used[i] = false;
}
}
}
backtrack();
return result;
}
// Test
console.log(getPermutation(3, 3)); // Output: 213let count = 0Tracks how many permutations generatedlet result = ''Stores the k-th permutation once foundif (path.length === n)Base case: full permutation formedcount++Increment count when a permutation is completedif (count === k)Check if current permutation is the k-thresult = path.join('')Save the k-th permutationif (result) returnStop recursion early if k-th permutation foundfor (let i = 0; i < n; i++)Try each number from 1 to nused[i] = trueMark number as usedpath.push(i + 1)Add number to current pathbacktrack()Recurse deeperpath.pop()Backtrack by removing last numberused[i] = falseMark number as unusedO(k * n)O(n)In worst case, we generate up to k permutations, each of length n, so O(k * n).
Early stopping helps but still too slow for large inputs; motivates direct computation.
Intuition
Each position in the permutation can be determined by dividing k by factorials of remaining digits, selecting the appropriate number from the unused list.
Algorithm
- Precompute factorials from 0! to (n-1)!.
- Create a list of numbers from 1 to n.
- Adjust k to zero-based index (k = k - 1).
- For each position, determine the index by dividing k by factorial of remaining positions.
- Append the number at that index to the result and remove it from the list.
- Update k to k modulo factorial of remaining positions.
- Return the concatenated result string.
def getPermutation(n, k):
factorial = [1] * n
for i in range(1, n):
factorial[i] = factorial[i - 1] * i
numbers = list(range(1, n + 1))
k -= 1 # zero-based index
result = []
for i in range(n - 1, -1, -1):
idx = k // factorial[i]
k %= factorial[i]
result.append(str(numbers[idx]))
numbers.pop(idx)
return ''.join(result)
# Driver code
if __name__ == '__main__':
print(getPermutation(3, 3)) # Output: '213'factorial = [1] * nInitialize factorial array to store factorials up to (n-1)!for i in range(1, n)Compute factorial values iterativelynumbers = list(range(1, n + 1))List of available numbers to pick fromk -= 1Convert k to zero-based index for calculationfor i in range(n - 1, -1, -1)Iterate from highest factorial to lowestidx = k // factorial[i]Determine index of number to pick based on kk %= factorial[i]Update k to remainder for next iterationresult.append(str(numbers[idx]))Add selected number to resultnumbers.pop(idx)Remove used number from listimport java.util.*;
public class Solution {
public String getPermutation(int n, int k) {
int[] factorial = new int[n];
factorial[0] = 1;
for (int i = 1; i < n; i++) {
factorial[i] = factorial[i - 1] * i;
}
List<Integer> numbers = new ArrayList<>();
for (int i = 1; i <= n; i++) {
numbers.add(i);
}
k--;
StringBuilder result = new StringBuilder();
for (int i = n - 1; i >= 0; i--) {
int idx = k / factorial[i];
k %= factorial[i];
result.append(numbers.get(idx));
numbers.remove(idx);
}
return result.toString();
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.getPermutation(3, 3)); // Output: 213
}
}int[] factorial = new int[n]Array to store factorials up to (n-1)!for (int i = 1; i < n; i++)Compute factorial values iterativelyList<Integer> numbers = new ArrayList<>()List of available numbers to pick fromk--Convert k to zero-based indexfor (int i = n - 1; i >= 0; i--)Iterate from highest factorial to lowestint idx = k / factorial[i]Determine index of number to pickk %= factorial[i]Update k for next iterationresult.append(numbers.get(idx))Add selected number to resultnumbers.remove(idx)Remove used number from list#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Solution {
public:
string getPermutation(int n, int k) {
vector<int> factorial(n, 1);
for (int i = 1; i < n; i++) {
factorial[i] = factorial[i - 1] * i;
}
vector<int> numbers;
for (int i = 1; i <= n; i++) {
numbers.push_back(i);
}
k--;
string result;
for (int i = n - 1; i >= 0; i--) {
int idx = k / factorial[i];
k %= factorial[i];
result += to_string(numbers[idx]);
numbers.erase(numbers.begin() + idx);
}
return result;
}
};
int main() {
Solution sol;
cout << sol.getPermutation(3, 3) << endl; // Output: 213
return 0;
}vector<int> factorial(n, 1)Stores factorials up to (n-1)!for (int i = 1; i < n; i++)Compute factorial values iterativelyvector<int> numbersList of available numbers to pick fromk--Convert k to zero-based indexfor (int i = n - 1; i >= 0; i--)Iterate from highest factorial to lowestint idx = k / factorial[i]Determine index of number to pickk %= factorial[i]Update k for next iterationresult += to_string(numbers[idx])Add selected number to resultnumbers.erase(numbers.begin() + idx)Remove used number from listfunction getPermutation(n, k) {
const factorial = new Array(n).fill(1);
for (let i = 1; i < n; i++) {
factorial[i] = factorial[i - 1] * i;
}
const numbers = [];
for (let i = 1; i <= n; i++) {
numbers.push(i);
}
k--;
let result = '';
for (let i = n - 1; i >= 0; i--) {
const idx = Math.floor(k / factorial[i]);
k %= factorial[i];
result += numbers[idx];
numbers.splice(idx, 1);
}
return result;
}
// Test
console.log(getPermutation(3, 3)); // Output: 213const factorial = new Array(n).fill(1)Array to store factorials up to (n-1)!for (let i = 1; i < n; i++)Compute factorial values iterativelyconst numbers = []List of available numbers to pick fromk--Convert k to zero-based indexfor (let i = n - 1; i >= 0; i--)Iterate from highest factorial to lowestconst idx = Math.floor(k / factorial[i])Determine index of number to pickk %= factorial[i]Update k for next iterationresult += numbers[idx]Add selected number to resultnumbers.splice(idx, 1)Remove used number from listO(n^2)O(n)Computing factorials is O(n), and removing elements from the list is O(n) per removal, total O(n^2).
This is the optimal approach for this problem and is expected in interviews.
| Approach | Time | Space | Stack Risk | Reconstruct | Use In Interview |
|---|---|---|---|---|---|
| 1. Brute Force | O(n! * n) | O(n! * n) | Yes (deep recursion) | Yes | Mention only - never code |
| 2. Backtracking with Early Stop | O(k * n) | O(n) | Yes | Yes | Mention as improvement, but not optimal |
| 3. Optimal Direct Computation | O(n^2) | O(n) | No | N/A | Code this approach |
How to Present
Step 1: Clarify the problem and constraints with the interviewer.Step 2: Explain the brute force approach to show understanding.Step 3: Discuss its inefficiency and propose early stopping.Step 4: Introduce the factorial number system for direct computation.Step 5: Code the optimal solution and test with examples.
Time Allocation
What the Interviewer Tests
The interviewer tests your understanding of permutations, ability to optimize brute force, and knowledge of factorial math for direct computation.
Common Follow-ups
- What if numbers are not 1 to n but an arbitrary list? → Sort and apply same logic.
- How to handle duplicates in the input? → Use backtracking with pruning or count permutations carefully.
When to Use
1) Asked for k-th permutation or sequence; 2) Input size too large for brute force; 3) Lexicographical order mentioned; 4) Factorials or counting permutations involved.
