一、简介
包含全排列问题、包含重复元素的全排列问题、以及它们的递归和非递归实现、还有如何寻找字典顺序的下一个排列、以及寻找第k个排列数。
二、内容
1、全排列问题(递归解法)
描述
给定一个数组nums,要求给出所有排列情况。
例子:nums = {1,2,3},返回结果为
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
思路
对于以上nums = {1,2,3}的例子,我们对全排列的情况做一个分类:
①以1开头,后面跟着{2,3}的全排列
②以2开头,后面跟着{1,3}的全排列
③以3开头,后面跟着{1,2}的全排列
而对于{2,3}、{1,3}、{1,2},我们又可以重复使用以上的思路,下面以{2,3}为例分析:
①以2开头,后面跟着{3}的全排列
②以3开头,后面跟着{2}的全排列
而对于{2}、{3}这些只有一个数的集合,它们的全排列就是它们本身。这也就是递归的终止条件。
以上的思路,用代码按如下方式实现:
对于一个有n个元素的数组a,假设其n个元素分别为a[n-1]。
第一步:
将其分成两个部分,第一个元素a[0]和后面其他元素,全排列表示以a[0]为首的所有排列,等于a[0]加上后面部分的全排列
第二步:
使用a[0]和后面的a[1]交换,同样,得到了以a[1]为首的所有全排列。
第三步:
交换回原来的序列,然后再交换a[0]和a[2]。这样能得到以a[2]为首的所有排列,然后再交换回初始状态
第四步:
按上面的交换--求排列--再交换回来的流程分别求出以a[3]-a[n-1]为首的所有排列。
这样就得到了所有的排列情况。
注意:对后面部分的排列情况调用递归实现,返回条件是当后面部分只有一个元素的时候。
实现:
void get_permute(vector& nums, int pos, vector>&result) {if (nums.size() ==pos) {
result.push_back(nums);return;
}for (int i = pos; i < nums.size(); i++) {
swap(nums[pos], nums[i]);
get_permute(nums,pos+1,result);
swap(nums[i], nums[pos]);
}
}
vector> permute(vector&nums) {
vector>result;
get_permute(nums,0,result);returnresult;
}//这是测试用例
intmain()
{
vector v = {1,1,3};
auto result=permute(v);for(auto i : result) {for(auto j : i)
cout<
cout<
}
system("pause");return 0;
}
2、含有重复元素的全排列问题(递归解法)
描述:
假如给定的数组有重复元素,比如nums={1,1,2},要求求出全排列,不能有重复。
思路:
考虑其中任意一个递归时的情况,假设其第一部分为a[i],第二部分为a[i+1]到a[n-1]。在这种情况,按照上面的描述将数组分为两个部分求解全排列。(我们假设有元素a[j],其中i+1 ≤ j ≤ n-1)
重复的情况分为以下两种:
①a[i] = a[j]时,a[i]和a[j]互换得到的序列与之前一样,再求一遍全排列会有重复
②a[j] = a[j2](j2和j取同样范围,但j != j2)。当a[i]与a[j]互换后得到的序列与a[i]与a[j2]互换得到的序列相等,分别对他们求全排列也会出现重复
于是,在上述解法的代码中,get_permute函数的for循环中加入限制条件,让他们跳过这两种情况。
实现:
void get_permuteUnique(vector& nums, int pos, vector>&result) {if (nums.size() ==pos) {
result.push_back(nums);return;
}for (int i = pos; i < nums.size(); i++) {//第一种重复,位置pos和位置i的数重复
if (i != pos && nums[i] == nums[pos]) continue;//第二种重复,位置pos和位置i的数不重复//但pos之后有两个位置i和j,nums[i]和nums[j]有重复,导致pos和同一个数交换了两次
bool find = false; //用find来指示是否找到了这样的i和j
if(i !=pos){int temp =i;while (temp
find= true;break;
}
temp++;
}
}if (find == true)continue;//除去重复结束
swap(nums[pos], nums[i]);
get_permuteUnique(nums, pos+ 1, result);
swap(nums[i], nums[pos]);
}
}
vector> permuteUnique(vector&nums) {
vector>result;
get_permuteUnique(nums,0, result);returnresult;
}//这是测试用例
intmain()
{
vector v = {1,1,3};
auto result=permuteUnique(v);for(auto i : result) {for(auto j : i)
cout<
cout<
}
system("pause");return 0;
}
3、下一个排列(在讲解非递归实现之前的问题)
描述:
给定一个数,按字典顺序找出下一个排列。(如果给出是最大的,则返回最小)
例子:123,下一个比123大的排列是132
321,这是最大的数,返回最小的数123
115,下一个比112大的数是152
思路:
第一步:我们从后往前遍历,寻找第一个位置i,其中i满足nums[i]
第二步:从i处向后寻找,找到一个比i处数更大,但是和i处的数之差最小的位置j。要得到下一个排列数,一定要使换到u处的数尽可能小,但是要大于i处的数。
第三步:调换i和j处的数。这样确保了i处的数是最合适的。因为此时数组后半部分中,j处的数是所有大于i处的数中最小的
第四部:对i+1到末尾的数据进行从小到大排序。最高位替换成了更大的数,故后面的数要尽可能的小,大的数要往后排。
实现:
void nextPermutation(vector&nums) {int last =INT_MIN;inti;for (i = nums.size() - 1; i >= 0; i--) {if (nums.at(i)
last=nums.at(i);
}//判断数据是否是最大排列数,如果是,返回最小排列数
if (i == -1) {
sort(nums.begin(), nums.end());return;
}int min_pos =i;int min =INT_MAX;for (int j = i; j < nums.size(); j++) {if (nums.at(j) - nums.at(i)>0&& nums.at(j) - nums.at(i)
min_pos=j;
}
swap(nums.at(i), nums.at(min_pos));
sort(nums.begin()+i+1,nums.end());
}//这是测试用例
intmain()
{
vector v = {2,3,1};
nextPermutation(v);for(auto k : v) {
cout<< k <
}
system("pause");return 0;
}
4、全排列的非递归实现(不含重复元素)
思路:
上面我们描述了如何寻找下一个排列,嗯,那是非递归实现的,我们只需要对先数进行排序,然后依次调用这个函数就可以了。看实现。
实现:
void nextPermutation(vector& nums,bool&end) {int last =INT_MIN;inti;for (i = nums.size() - 1; i >= 0; i--) {if (nums.at(i)
last=nums.at(i);
}if (i == -1) {
end= true;return;
}int min_pos =i;int min =INT_MAX;for (int j = i; j < nums.size(); j++) {if (nums.at(j) - nums.at(i)>0&& nums.at(j) - nums.at(i)
min_pos=j;
}
swap(nums.at(i), nums.at(min_pos));
sort(nums.begin()+i+1,nums.end());
}
vector> permuteNoRecursion(vector&nums) {
vector>result;bool end = false;
sort(nums.begin(),nums.end());while (end == false) {
vector thistime =nums;
result.push_back(thistime);
nextPermutation(nums,end);
}returnresult;
}//这是测试用例
intmain()
{
vector v = {1,2,3};
auto result=permuteNoRecursion(v);for(auto i : result) {for(auto j : i)
cout<
cout<
}
system("pause");return 0;
}
5、全排列的非递归实现(含重复元素)
思路:
和上面一样利用nextPermutation函数,只是在得到排列之后判断一下是否与之前的排列一样。为此实现了一个简单判断vector是否一致的函数check。应该很好懂了。直接上代码
实现:
void nextPermutation(vector& nums,bool&end) {int last =INT_MIN;inti;for (i = nums.size() - 1; i >= 0; i--) {if (nums.at(i)
last=nums.at(i);
}if (i == -1) {
end= true;return;
}int min_pos =i;int min =INT_MAX;for (int j = i; j < nums.size(); j++) {if (nums.at(j) - nums.at(i)>0&& nums.at(j) - nums.at(i)
min_pos=j;
}
swap(nums.at(i), nums.at(min_pos));
sort(nums.begin()+i+1,nums.end());
}//判断两个vector是否相等
bool check(vector& nums1, vector&nums2) {if (nums1.size() != nums2.size()) return false;for (int i = 0; i < nums1.size(); i++)if (nums2[i] !=nums1[i])return false;return true;
}//主逻辑函数
vector> permuteNoRecursion(vector&nums) {
vector>result;
vectorthistime;bool end = false;
sort(nums.begin(),nums.end());while (end == false) {if (check(thistime, nums) == false) {
thistime=nums;
result.push_back(thistime);
}
nextPermutation(nums,end);
}returnresult;
}//这是测试用例
intmain()
{
vector v = {1,1,3};
auto result=permuteNoRecursion(v);for(auto i : result) {for(auto j : i)
cout<
cout<
}
system("pause");return 0;
}
6、寻找第k个排列(不含重复元素)
第一种:使用前面提到的寻找下一个排列,循环k次,得到第k个排列
第二种:使用排列顺序的规则来直接得到数字
假设有n=4个数,nums={1,2,3,4}
我们可以得到它的有序排列为,共有n!个排列
以1开头
1234
1243
1324
1342
1423
1432
以2开头
2134
2143
2314
2341
2413
2431
以3开头
3124
3142
3214
3241
3412
3421
以4开头
4123
4132
4213
4231
4312
4321
我们首先看第一个位置的数,假设我们要得到第10个排列,以每一个数开头的排列都有(n-1)!种,使用10除(n-1)!,得10/(n-1)! = 1,即第一个数为1,由于10%(n-1)!=4,所以我们要求的排列数在以2开头的排列中位于第四。
接着,n减一,我们使用上一步的余数4/2!得到2,即开头第一个数应该是第二个(由于2在第一个数已经被用到了,所以第二个数应该为3)。
接着后面只有两位,该规律到两位的时候就不符合了。直接判断得出结果。
代码中,使用map的第二个值来表示该数书否已经用过了,而getn函数用来求阶乘
int getn(intn) {int result = 1;for (int i = 1; i <= n; ++i)
result= result *i;returnresult;
}string getPermutation(int n, intk) {--k;
map m = { {1,1},{2,1},{3,1},{4,1},{5,1},{6,1},{7,1},{8,1},{9,1} };string result = "";for (int i = 1; i <= n; ++i) {intpos;//除的方式到最后两项就不成立了,当剩余两项的时候,直接赋值
if (n - i > 1)
pos= k / (getn(n - i)) + 1;else if (n - i == 1)
pos= k + 1;else if (n - i == 0)
pos= 1;int j = 0;//寻找第pos个数,如果遇到map中第二个数为0,则表示该数已经被用过了
for (; j < pos; ++j) {if (m[j+1] == 0)++pos;
}
result+=to_string(j);
m[j]= 0;if(n - i > 1)
k= k % getn(n -i);
}returnresult;
}//这是测试用例
intmain()
{for (int i = 1; i <= 24; ++i) {
cout<< getPermutation(4, i) <
}
system("pause");return 0;
}