[LeetCode] “全排列”问题系列(二) - 基于全排列本身的问题,例题: Next Permutation , Permutation Sequence

一、开篇

既上一篇<交换法生成全排列及其应用> 后,这里讲的是基于全排列 (Permutation)本身的一些问题,包括:求下一个全排列(Next Permutation);求指定位置的全排列(Permutation Sequence);给出一个全排列,求其所在位置。

 

二、例题

1. 求下一个全排列,Next permuation

Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers.

If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order).

The replacement must be in-place, do not allocate extra memory.

Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column.
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

class Solution {
public:
    void nextPermutation(vector<int> &num) {
    }
};

题目的意思是假设所有的全排列按照字典顺序被排列,找给定全排列的下一个。

我的思路是:所谓下一个全排列,其实就是将当前vetor的一个子集重排而已。这个子集如何划分?

考虑排列1237456,它的下一个排列是1243567,找子集的方法其实就是:从头到尾找到最后一个满足 num[i+1] > num[i] 的一对(实现时可以从尾到头遍历,找到第一个num[i+1] > num[i] 的一对),需要重排的子集就是 num[i~size-1] 这个子集。这个子集num[i~size-1] 满足一个条件:前两个元素递增,后面都是递减或者后面已经没有元素。特殊情况是:如果找不到这样的存在递增关系的 num[i] 和 num[i+1],说明整个序列都是降序,也就是没有更大的排列了,根据题目要求,直接将序列逆序即可。

重新排列的方式就是从num[i+1 ~ size-1]中选一个比num[i] 大的最小元素,将其和num[i] 交换,然后将num[+1 ~ size-1]逆序。

代码:

class Solution {
public:
    void nextPermutation(vector<int> &num) {
        int size = num.size();
        if(size == 0 || size == 1) return;
        int ascHead = size - 2; //最后一个num[i] < num[i+1] 的 i
        for(; ascHead >= 0 && num[ascHead] >= num[ascHead + 1]; --ascHead);
        if(ascHead < 0){reverse(num, 0, size-1); return;}
        int insert = size - 1; //存储那个比num[ascHead]大的最小值的index
        for(; insert > ascHead && num[insert] <= num[ascHead]; --insert);
        swap(num, ascHead, insert);
        reverse(num, ascHead+1, size-1);
    }
private:
    void swap(vector<int> &num, int left, int right){
        int temp = num[left];
        num[left] = num[right];
        num[right] = temp;
    }
    
    void reverse(vector<int> &num, int start, int end){
        while(start < end){
            swap(num, start++, end--);
        }
    }
};

 

2. 给一个全排列,求其在所有全排列中位于第几位

这道题上LeetCode上没有例题。

例如给定一个排列 356421, 因为第一位为3,因此1 和 2 开头的全排列已经经过了,以1开头的全排列个数为5!,2也是。因此该全排列的排名 > 2 * 5!

第二位为5,对于以3开头的全排列,排在35前面的有31,32,34开头的三个全排列。在356421中,5右边比5小的也正是1,2,4。

我们可以发现:序列长度为n,对于给定排列P某位上的数,假设这个数在P上从右起排第m位,我们只要看看该数右侧的位数上还有几个比它小的,就知道该数以右的部分在对应所有子序列中的排名了。

因此P的总排名 = ∑k*m! (m从0到n-1,k表示第m+1位之后有多少小于第m+1位个数。

 

3. 求指定位置的全排列

Permutation Sequence

The set [1,2,3,…,n] contains a total of n! unique permutations.

By listing and labeling all of the permutations in order,
We get the following sequence (ie, for n = 3):

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

Given n and k, return the kth permutation sequence.

Note: Given n will be between 1 and 9 inclusive

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

 

有了上一题的思路,这一題就很容易了。

无非就是不断除以m!,m在递减,将余数作为分母继续循环。

class Solution {
public:
    string getPermutation(int n, int k) {
        bool dig[n];
        unsigned int mul = 1;
        int j = 0, ind = 0, i = 0;
        string res = "";
        for(i = 0; i < n; mul *= (i>0?i:1), dig[i] = false, ++i);
        --k; //将k偏移,将0作为第一位
        for(i = n; i > 0; --i, k %= mul, mul /= (i>0?i:1)){
            ind = k/mul;
            for(j = 0; j < n; ++j){
                if(!dig[j]) --ind;
                if(ind < 0) break;
            }
            dig[j] = true;
            res.push_back(j + '1'); //这里j从0开始算,因此转化成字符要+'1'
        }
        
        return res;
    }
};

 

三、总结:

基于全排列本身可以出不少题目,但是我们只要知道全排列的数量是按照 k! 计算的,就可以从这里入手。

如果想构造全排列,上一文中的交换法就是一个思路

posted on 2014-11-02 04:32  Felix Fang  阅读(1277)  评论(2编辑  收藏  举报

导航