全排列相關總結

前言

    今天做LeetCode裏面的關於全排列的題目:31. Next Permutation發現自己對全排列是完全不瞭解。所以狠搜一波。總結一些知識點。(PS:該總結中待排列元素假定爲整型數字。由於個人習慣,right通常指左邊的元素,left通常指相對右的元素);

下一個排列(next permutation)

        給你一個排列,讓你求該排列在全排列序列種的下一個排列。如果是最後一個排列則輸出第一個排列;比如{1,2,3}的下一個排列是{1,3,2}。{3,2,1}的下一個排列是{1,2,3}。
        算法1:
        1. 從數組末尾向前查找相鄰元素記爲right和left。該相鄰元素滿足right<left。
        2. 再從末尾向前查找第一個大於right的元素記爲m。做swap(right,m)操作。
        3. 將right之後的所有元素做反轉操作。
        代碼:
class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int i = nums.size() - 1;
        while(i > 0 && nums[i] <= nums[i-1])
            i--;
        i--;
        if(i >= 0)
        {
            int j = nums.size() - 1;
            while(nums[i] >= nums[j]){
                j--;
            }
            swap(nums[i], nums[j]);
            sort(nums.begin() + i+1, nums.end());//反轉操作可以使用排序完成,因爲right後面的元素均爲逆序
        }else{
            sort(nums.begin(), nums.end());//同上
        }
    }
};

上一個排列(pre_permutation)

        給定一個序列,求該序列在全排列序列中前一個排序。算法和下一個排列很相近,操作差不多,將right<left改成right>left。然後第二個操作改成找到以一個小於right的數交換。接下來操作相同。直接看代碼!

bool prePermutation(vector<int> &nums)
{
	int size = nums.size();
	for (int i = size - 1; i > 0; --i)
	{
		if (nums[i] < nums[i - 1])
		{
			int val = nums[i - 1];
			int j = size - 1;
			for (; j >= i; --j)
			{
				if (nums[j] < val)
					break;
			}
			swap(nums[i - 1], nums[j]);
			int l = i;
			int r = size - 1;
			while (l < r)
			{
				swap(nums[l], nums[r]);
				l++;
				r--;
			}
			return true;
		}
	}
	int start = 0;
	int last = size - 1;
	while (start < last)
	{
		swap(nums[start], nums[last]);
		start++;
		last--;
	}
	return false;
}

第n個排列(n'st Permutation)

        當給定一系列元素,(有無重複元素是否有影響?)直接給出全排列序列中的第n個。不能從第一個生成至第n個。

        無重複元素求第n個排列有一個方法——康拓展開

        公式:        X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0!;

        操作說明: {1,2,3,4,5} 的第16個排列。

        分析:第16個排列說明該排列前面有15排列。

        1. 15/(5-1)! = 0 ......15 即第一位數字在數字集合中有0個數字比它小=> 1

        2. 取上一次操作的餘數15/(5-2)!  = 2 ......3 即第二位數字在數字集合中有2個比它小 => 4(1已經取出)

        3. 3/(5-3)! = 1 ......1  => 3

        4. 1/(5-4)! = 1 ......0  => 5

        5. 最後剩下2

        總結得 排列爲 {1,4,3,5,2}。

        代碼:
int  fac[] = {1,1,2,6,24,120,720,5040,40320};//階乘提前處理!
//康託展開的逆運算,{1...n}的全排列,中的第k個數爲s[]
void reverse_kangtuo(int n,int k,char s[])
{
    int i, j, t, vst[8]={0};
    --k;
    for (i=0; i<n; i++)
    {
        t = k/fac[n-i-1];
        for (j=1; j<=n; j++)
            if (!vst[j])
            {
                if (t == 0) break;
                --t;
            }
        s[i] = '0'+j;
        vst[j] = 1;
        k %= fac[n-i-1];
    }
}

排列是第幾?(index of permutatioin)

        當知道一系列元素全排列中的某一個排列,想知道這個排序是第幾個排列。如果使用next_permutation從第一個計算這時複雜度爲O(n*n)。可以使用康拓展開來計算,第幾個排列使用的是康拓展開的逆。

        操作示例:{1,2,3,4,5} 中的一個排列{1,4,3,5,2}。

        1.  第一個1在集合中有0個比他小,則index+=0*(5-1)! => index = 0;

        2. 第二個4在集合中有2個比它小, 則index+=2*(5-2)! => index = 12;

        3. 第三個3在集合中有1個比它小, 則index+=1*(5-3)! => index = 14;

        4. 第四個5在集合中有1個比它小, 則index+=1*(5-4)! => index = 15;

        5. 第五個爲最後一個爲0.

        總結:上面逆的使用時做減一操作,這裏做加一操作。即index ++ =>index = 16。 說明{1,4,3,5,2}是{1,2,3,4,5}的全排列中的第16個排列。

        代碼:
int  fac[] = {1,1,2,6,24,120,720,5040,40320}; //i的階乘爲fac[i]
// 康託展開-> 表示數字a是 a的全排列中從小到大排,排第幾
// n表示1~n個數  a數組表示數字。
int kangtuo(int n,char a[])
{
    int i,j,t,sum;
    sum=0;
    for( i=0; i<n ;++i)
    {
        t=0;
        for(j=i+1;j<n;++j)
            if( a[i]>a[j] )
                ++t;
        sum+=t*fac[n-i-1];
    }
    return sum+1;
}




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章