b_lc_第k个排列(暴搜 / 数学剪枝)
给出集合 [1,2,3,…,n],其所有元素共有 n! 种排列。
按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:
"123"
"132"
"213"
"231"
"312"
"321"
给定 n 和 k,返回第 k 个排列。
说明:
给定 n 的范围是 [1, 9]。
给定 k 的范围是[1, n!]。
输入: n = 4, k = 9
输出: "2314"
方法一:暴搜
思路
利用位掩码来记录数字的选择状态
class Solution {
public:
string ans;
int vis[10];
void dfs(string& cur, int mask, int n, int& k) {
if (cur.size() == n) {
k--;
if (!k) ans=cur;
return;
}
for (int j=1; j<=n; j++) if ((mask & (1 << j-1)) == 0) {
cur += to_string(j);
dfs(cur, mask | (1<<j-1), n, k);
cur.pop_back();
if (!k) return;
}
}
string getPermutation(int n, int k) {
string cur="";
memset(vis, 0, sizeof vis);
dfs(cur, 0, n, k);
return ans;
}
};
复杂度分析
- 时间复杂度:\(O(n!)\),
- 空间复杂度:\(O(k)\),
方法二:数学剪枝
比如 n=4,k=9 时:
- 假设第一位选择 1,则后面由 2、3、4 组成的排列共 3! 种,9 > 6 故 1 不可取
- cur = "2",假设第一位选择 2,则后面由 1、3、4 组成的排列共 3! 种,但经过上面一层的过滤,此时剩下排列只有 9-6=3 种,而 3! > 3,故,k=9 时的全排列一定在以 2 为开头的排列中(k=3)
- cur = "21" 由于 1 没有选,接下来如果选 1 的话,后面的 3、4 可以组成的排列则有 2! 个,但 \(3-2 > 1\) 故,1 也不可选,这里继续删除以 "21" 为开头的两个排列 "2134"、"2143"(k=1)
- cur = "213" ,由于 3 没有选,接下来如果选 3 的话,此时只剩下的 4 组成的排列有 1 种,而 1-1 = 0,也就是 k=0 啦
- cur = "21" 由于 1 没有选,接下来如果选 1 的话,后面的 3、4 可以组成的排列则有 2! 个,但 \(3-2 > 1\) 故,1 也不可选,这里继续删除以 "21" 为开头的两个排列 "2134"、"2143"(k=1)
注:假设从 n+1 层回退到上一层 n 的时候,由于第 n 层的选择是无效的,所以 vis 数组对 n+1 层选择的数字不会做出撤销
class Solution {
public:
string s;
int n,st[10],fac[10];
void dfs(int& k) {
if(s.size()==n){
return;
}
int c=fac[n-s.size()-1];
for (int j=1; j<=n; j++) if (!st[j]) {
if (k>c) {
k-=c;
} else {
st[j]=1, s+=to_string(j);
dfs(k);
}
}
}
string getPermutation(int n, int k) {
fac[0]=1;
for (int i=1; i<=n; i++) fac[i]=fac[i-1]*i;
this->n=n;
dfs(k);
return s;
}
};
复杂度分析
- 时间复杂度:\(O(k)\),
- 空间复杂度:\(O(n)\),