LeetCode-----第六十题-----第k个排列

第k个排列

难度:中等

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

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

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

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

说明:

  • 给定 n 的范围是 [1, 9]。
  • 给定 的范围是[1,  n!]。

示例 1:

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

示例 2:

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

 

这里先介绍两个函数及用法:next_permutation和prev_permutation

函数原型:

next_permutation:

1.template< class BidirIt >bool next_permutation( BidirIt first, BidirIt last );
2.template< class BidirIt, class Compare >bool next_permutation( BidirIt first, BidirIt last, Compare comp );

prev_permutation:

1.template< class BidirIt >bool prev_permutation( BidirIt first, BidirIt last);
2.template< class BidirIt, class Compare >bool prev_permutation( BidirIt first, BidirIt last, Compare comp);

参数

first,end ——重新排序的元素范围

comp —— 自定义比较函数(默认字典排序)

顾名思义,next_permutation就是求下一个排列组合,而prev_permutation就是求上一个排列组合。首先我们必须了解什么是“下一个”排列组合,什么是“前一个”排列组合。考虑由三个字符所组成的序列{a,b,c}。

那么按照字典序排升序他们一共有下面这几种排列方式:

  • abc
  • acb
  • bac
  • bca
  • cab
  • cba

如果给定排列方式P,令P为{acb},那么next_permutation即求P+1也就是{bac},prev_permutation也就是求P-1即为{abc}。当然也可以自定义谓词函数进行自定义的“下一个排列组合”。

代码演示

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;


int main(){
    int a[] = {1,2,3};
    do{
        for(int i = 0; i < 3; i++) cout << a[i] << ' ';
        cout << endl;
    }while(next_permutation(a,a+3));

    cout << endl;
    do{
        for(int i = 0; i < 3; i++) cout << a[i] << ' ';
        cout << endl;
    }while(prev_permutation(a,a+3));
    return 0;
}

题目分析:

       第K个排列,讲道理是用回溯法全排列,然后确定。这里遇到两个问题:1.全排列一遍会超时;2.会发现常用的全排列还不是字典顺序。所以提供两种思路:1.利用STL中的next_permutation直接求(时间复杂度较高);2.找数学规律

找数学规律:

解题思路:

这道题是让求出n个数字的第k个排列组合,由于其特殊性,我们不用将所有的排列组合的情况都求出来,然后返回其第k个,我们可以只求出第k个排列组合即可,那么难点就在于如何知道数字的排列顺序,可参见https://bangbingsyb.blogspot.com/2014/11/leetcode-permutation-sequence.html

首先我们要知道当n = 3时,其排列组合共有3! = 6种,当n = 4时,其排列组合共有4! = 24种,我们就以n = 4, k = 17的情况来分析,所有排列组合情况如下:

1234
1243
1324
1342
1423
1432
2134
2143
2314 
2341
2413
2431
3124
3142
3214
3241
3412 <--- k = 17
3421
4123
4132
4213
4231
4312
4321

我们可以发现,每一位上1,2,3,4分别都出现了6次,当第一位上的数字确定了,后面三位上每个数字都出现了2次,当第二位也确定了,后面的数字都只出现了1次,当第三位确定了,那么第四位上的数字也只能出现一次,那么下面我们来看k = 17这种情况的每位数字如何确定,由于k = 17是转化为数组下标为16:

最高位可取1,2,3,4中的一个,每个数字出现3!= 6次,所以k = 16的第一位数字的下标为16 / 6 = 2,即3被取出
第二位此时从1,2,4中取一个,k = 16时,k' = 16 % (3!) = 4,而剩下的每个数字出现2!= 2次,所以第二数字的下标为4 / 2 = 2,即4被取出
第三位此时从1,2中去一个,k' = 4时,k'' = 4 % (2!) = 0,而剩下的每个数字出现1!= 1次,所以第三个数字的下标为 0 / 1 = 0,即1被取出
第四位是从2中取一个,k'' = 0时,k''' = 0 % (1!) = 0,而剩下的每个数字出现0!= 1次,所以第四个数字的下标为0 / 1= 0,即2被取出

LeetCode-----第六十题-----第k个排列_第1张图片

参考代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

class Solution {
public:
	string getPermutation(int n, int k) {
		string str = "123456789";  //建立待排列的字符串
		string res;				   //存放结果
		vector f(n,1);		   //初始化阶乘的一个数组,这里需要注意的是1!=1,0!=1
		--k;					   //将k转换成下标
		for (int i = 1; i < n; i++)//阶乘赋值
		{
			f[i] = f[i - 1] * i;	//需要注意的是1!=1,0!=1
		}

		for (int j = n; j >= 1; --j)  //要注意的是这边从第一个数开始选,阶乘要从(n-1)!开始
		{
			int temp = k / f[j - 1];
			k = k % f[j-1];
			res.push_back(str[temp]);
			str.erase(temp, 1);
		}
		return res;
	}
};

int main(void)
{
	Solution my_class;

	cout << my_class.getPermutation(4,9) << endl;
	
	system("pause");
	return 0;
}

 

参考代码(第一种方案):

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

 

你可能感兴趣的:(LeetCode-----第六十题-----第k个排列)