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 啦

注:假设从 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)\)
posted @ 2020-09-05 19:49  童年の波鞋  阅读(118)  评论(1编辑  收藏  举报