leetcode:60. 排列序列

题目来源

  • leetcode:60. 排列序列

题目描述

leetcode:60. 排列序列_第1张图片
leetcode:60. 排列序列_第2张图片

class Solution {
public:
    std::string getPermutation(int n, int k){
        
    }
};

题目解析

思路

找规律

对于 n 个不同的元素(例如数 1,2,⋯,n),它们可以组成的排列总数目为 n!

对于给定的n和k,我们不妨从左往右确定第k个排列中的每一个位置上的元素到底是什么。

我们首先确定排列中的首个元素a1,那么:

  • 以1为a1的排列一共有 ( n − 1 ) ! (n - 1)! (n1)!
  • 以2为a1的排列一共有 ( n − 1 ) ! (n - 1)! (n1)!
  • 以n为a1的排列一共有 ( n − 1 ) ! (n - 1)! (n1)!

我们可以将它们看作不同的组。第k个排列落在哪个组,直接空降进去找,大大缩小了寻找的范围
leetcode:60. 排列序列_第3张图片
如上图,n = 4 ,k = 10,要找的是第 10 个。我们把数字都放到 nums 数组[1,2,3,4],索引 0 对应数字 1,有一个错位,所以把 k 减 1 处理。

(10-1) / 3! 的取整,等于索引 1,对应nums数组的2,因此,确定了第一个数字2。

leetcode:60. 排列序列_第4张图片
现在,剩下 3 个数字,[1, 3, 4],继续分组,如下图,更新一下 k,看看落入哪一组,重复上述步骤,确定出下一个数字,直到确定完 n 个数字。
leetcode:60. 排列序列_第5张图片

class Solution {
public:
    string getPermutation(int n, int k) {
        string num="123456789";
        string ans;
        int cur,f=1;
        // n的阶乘
        for(int i=1;i<=n;++i)
            f*=i;
        k=k-1; // 从0开始,便于mod
        while(n>0){
            f/=n; // n-1的阶乘
            cur=k/f; // 当前数
            k=k%f;   // 剩余
            ans.push_back(num[cur]); // 从num中拿出该数
            num.erase(cur,1); // num中删除该数
            --n;// 下一轮
        }
        return ans;
    }
};

回溯 + 剪枝

我们可以使用全排列的回溯搜索算法,依次得到全排列,输出第 kk 个全排列即可。事实上,我们不必求出所有的全排列。

基于以下几点考虑:

  • 所求排列一定在叶子节点处得到,进入每一个分支,可以根据已经选定的数的个数,进而计算还未选定的数的个数,然后计算阶乘,就可知道这一分支的叶子节点的个数:
    • 如果k大于这一分支将要产生的叶子节点数,就跳过这个分支
    • 如果k小于等于这一分支将要尝试的叶子节点数,说明所求的全排列一定在这一个分支将要产生的叶子结点里,需要递归求解。

leetcode:60. 排列序列_第6张图片
leetcode:60. 排列序列_第7张图片
leetcode:60. 排列序列_第8张图片
leetcode:60. 排列序列_第9张图片

class Solution {
    std::vector<bool> used;   // 记录数字是否使用过
    std::vector<int> factorial;  //阶乘数组
     int n;
     int k;

    /**
      * 计算阶乘数组
      *
      * @param n
      */
    void calculateFactorial(int n){
        factorial.resize(n + 1);
        factorial[0] = 1;
        for (int i = 1; i <= n; ++i) {
            factorial[i] = factorial[i - 1] * i;
        }
    }

    /**
    * @param index 在这一步之前已经选择了几个数字,其值恰好等于这一步需要确定的下标位置
    * @param path
    */
    void dfs(int idx, std::string &path){
        if(idx == n){
            return;
        }

        // 计算还未确定的数字的全排列的个数,第 1 次进入的时候是 n - 1
        int remain_fac = factorial[n - 1 - idx]; // 剩下的数的全排列的个数
        for (int i = 1; i <= n; ++i) {    // 遍历 [1, n]
            if(used[i]){  // 跳过已使用的数
                continue;
            }
            if (remain_fac > 0 && remain_fac < k) {  // 剩下的数的全排列个数小于当前 k ,说明第 k 个排列肯定不在当前的递归子树中,直接跳过该递归
                k -= remain_fac;
                continue;
            }
            path = path + static_cast<char>('0' + i);
            used[i] = true;
            dfs(idx + 1, path);   // 因为是一次递归直接到叶子,所以不需要还原状态
            // 注意 1:不可以回溯(重置变量),算法设计是「一下子来到叶子结点」,没有回头的过程
            // 注意 2:这里要加 return,后面的数没有必要遍历去尝试了
            return;
        }
    }
public:
    std::string getPermutation(int n, int k){
        this->n = n;
        this->k = k;
        calculateFactorial(n);

        used.resize( n + 1, false );

        std::string path;
        dfs(0, path);
        return path;
    }
};
  • 动画

类似题目

题目 思路
leetcode:46. 给定(无重复)数组nums,生成所有可能的全排列Permutations
leetcode:47. 给定(可重复)数组nums,生成所有可能的(不重复)全排列Permutations II
leetcode:31. 给定(可重复)数组nums,生成下一个排列(字典序) Next Permutation
leetcode:60. 给定集合[1…n],返回生成第k个序列(字典序) Permutation Sequence
leetcode:77. 给定集合[1…n],所有可能的 k 个数的组合 Combinations

你可能感兴趣的:(算法与数据结构,leetcode,算法,职场和发展)