[每日一题]112:排列序列


题目描述

给出集合 [1,2,3,…,n],其所有元素共有 n! 种排列。

按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:

  1. “123”
  2. “132”
  3. “213”
  4. “231”
  5. “312”
  6. “321”

给定 n 和 k,返回第 k 个排列。

示例 1:

输入:n = 3, k = 3
输出:"213"

示例 2:

输入:n = 4, k = 9
输出:"2314"

示例 3:

输入:n = 3, k = 1
输出:"123"

题解思路

方法一:调用 STL 库

代码实现:

class Solution {
    
    
public:
    string getPermutation(int n, int k) {
    
    
        string s = string("123456789").substr(0, n);
        for (int i = 1; i < k; ++i) {
    
    
            next_permutation(s.begin(), s.end());
        }
        return s;
    }
};

方法二:除法定位

例如: n = 6, k = 373;
初始化数组 nums = [1, 2, 3, 4, 5, 6]; v = {1, 1, 2, 6, 24, 120, 720, 5040, … };
首先应该明白,以 1 开头的全排列有 5! 个,以 2 开头的全排列有 5! 个 …… 共 5! * 6 = 6! 个

  • 故 k = 373 时,全排列的第一个数字应该是 nums[ k / 5! ] = 4 ;
  • 数组删除 4, 此时 nums = [1, 2, 3, 5, 6]; k = k % 5! = 12 ;
  • 接下来就是在 nums 中找第 12 个全排列,重复 1,2 步即可 。

代码实现:

class Solution {
    
    
public:
    string getPermutation(int n, int k) {
    
    
        vector<int> v(n);
        v[0] = 1;
        for (int i = 1; i < n; ++i) {
    
    
            v[i] = i * v[i - 1];
        }
        --k;
        string res;
        vector<int> nums;
        for (int i = 1; i <= n; ++i) {
    
    
            nums.push_back(i);
        }
        for (int i = 1; i <= n; ++i) {
    
    
            int order = k / v[n - i];
            res.push_back(nums[order] + '0');
            for (int j = order; j < n - i; j++) {
    
    
                nums[j] = nums[j + 1];
            }
            k %= v[n - i];
        }
        return res;
    }
};

代码实现:

class Solution {
    
    
    int mul(int num) {
    
    
        // 返回num的阶乘
        int res = 1;
        while (num > 1) {
    
    
            res *= num--;
        }
        return res;
    }
public:
    string getPermutation(int n, int k) {
    
    
        vector<int> v;   // 存放当前的数字
        for (int i = 1; i <= n; i++) {
    
    
            v.push_back(i);
        }
        string ans = "";
        --k;
        while (n) {
    
    
            int cur_sum = mul(n - 1);  //当前阶乘
            // k-1是边界的一个处理,比如n=3,k=4时,因为用的是闭区间[],不减一的话边界统一不起来,会成为[)
            int t = k / cur_sum;  //当前最高位要填v[t]
            ans += ('0' + v[t]);
            v.erase(v.begin() + t);
            k -= (t * cur_sum);
            n--;
        }
        return ans;
    }
};

方法三:康托编码

采用康托编码的思路。其实就是康托展开的逆过程。康托展开用来求某个全排列数是第几小的数,也就是当这些数按顺序排时第几个数。

过程如下:比如求321 是 第几小的,可以这样来想:小于3的数有1和2 两个,首位确定之后后面两位有2!中情况,所以共有 2*2!=4种。

小于2的数只有一个1,所以有11!=1种情况,最后一位是1,没有比一小的数,所以是0!=0

综上:小于321的数有4+1=5个,所以321是第六小的数。

逆过程就是已知这个数是第k个数,求这个数是多少,当然是知道n的值的。

第k个数就是有k-1个数比这个数小。

所以就是 k-1=an * (n-1)! + an-1 * (n-2)!+…+a1*0!;

再举一个例子:

如何找出第16个(按字典序的){1,2,3,4,5}的全排列?

  1. 首先用16-1得到15
  2. 用15去除4! 得到0余15
  3. 用15去除3! 得到2余3
  4. 用3去除2! 得到1余1
  5. 用1去除1! 得到1余0

有0个数比它小的数是1,所以第一位是1
有2个数比它小的数是3,但1已经在之前出现过了所以是4
有1个数比它小的数是2,但1已经在之前出现过了所以是3
有1个数比它小的数是2,但1,3,4都出现过了所以是5
最后一个数只能是2

所以排列为1 4 3 5 2

代码实现:

class Solution {
    
    
    // 得到 n 的阶乘
    int factorial(int n)
    {
    
    
        int res = 1;
        while (n > 1) {
    
    
            res *= n--;
        }
        return res;
    }

    string kth_permutation(string& s, int k)
    {
    
    
        const int n = s.size();
        string res;

        int base = factorial(n - 1);
        --k;
        for (int i = n - 1; i > 0; k %= base, base /= i, --i)
        {
    
    
            auto a = s.begin() + k / base;
            res.push_back(*a);
            s.erase(a);
        }
        res.push_back(s[0]);
        return res;
    }
public:
    string getPermutation(int n, int k) {
    
    
        string s(n, '0');
        for (int i = 0; i < n; i++)
        {
    
    
            s[i] += i + 1;
        }
        return kth_permutation(s, k);
    }
};

猜你喜欢

转载自blog.csdn.net/AngelDg/article/details/114377523
今日推荐