15 全排列
原题网址:https://www.lintcode.com/zh-cn/problem/permutations/#
给定一个数字列表,返回其所有可能的排列。
注意事项
你可以假设没有重复数字。
给出一个列表[1,2,3]
,其全排列为:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
使用递归和非递归分别解决。
法1.递归解法:
例如,nums=[1,2,3,4]求全排列,有四个位置
当第一个元素为1时,相当于求2,3,4的全排列。而在此排列中,当2为第一个元素时,相当于求3,4的全排列,当3为第一个元素时,……重复……然后又可以继续划分,3为第一个元素,4本身为一个全排列,4为第一个元素,3本身为一个全排列,此趟over;
当第一个元素为2时,相当于求1,3,4的全排列,而在此排列中,当1为第一个元素时,相当于求3,4的全排列,当3为第一个元素时,……重复……然后又可以继续划分,3为第一个元素,4本身为一个全排列,4为第一个元素,3本身为一个全排列,此趟over;
...................
上述过程中,很容易发现,该过程适用于递归,每趟都是一个递归。
算法理解的差不多了,但是要看懂并复现出代码,对我这样的菜鸟还是有点难度的。说一点自己的理解:
start是标示位,表示从start下标开始一直到最后一个元素需要进行全排列。
当start等于最后一个元素下标时,全排列即为数组本身,所以将数组push到结果中;
若start不等最后一个元素下标,以start为循环初始值,一直到最后一个元素循环停止,循环体内执行的正是上面算法分析中的每一趟的内容:
将第i个元素放到第一个坑里,全排列剩下的元素(递归),然后恢复数组初始状态,i向前移,循环继续……
class Solution { public: /* * @param nums: A list of integers. * @return: A list of permutations. */ vector<vector<int>> permute(vector<int> &nums) { // write your code here vector<vector<int>> result; if (nums.empty()) { result.push_back(nums); //此句不可少,否则输入[]时输出[],要求输出[[]]……; return result; } per(nums,0,result); return result; } void per(vector<int> &nums,int start,vector<vector<int>> &result) { if (start==nums.size()-1) { result.push_back(nums); } for (int i=start;i<(int)nums.size();i++) { swap(nums[start],nums[i]); per(nums,start+1,result); swap(nums[start],nums[i]); } } };
递归算法的另一个版本:https://blog.csdn.net/this_is_qiqi/article/details/77844400
法2.非递归(参考 https://blog.csdn.net/aphysia/article/details/77774105)
使用插入法,假如传进去的数字是1,2,3,那么先把1放进vector<int>中得到【1】,把【1】放到vector<vector<int>>(存放vector的vector)中得到【【1】】然后取出【【1】】里面的第一个数组【1】,往里面插入2,这个数字,有两种插法,就产生了两种排列【1,2】【2,1】,放进去得到【【1,2】,【2,1】】,然后取出【1,2】,插入3,有三种情况【3,1,2】,【1,3,2】,【1,2,3】,把它们放进去就是【【2,1】,【3,1,2】,【1,3,2】,【1,2,3】】,接着把【2,1】取出来,将3插进去,也有3中插法,就得到了最后的排列【【3,1,2】,【1,3,2】,【1,2,3】,【3,2,1】,【2,3,1】,【2,1,3】】,直到这里数组中的元素已经全部插入完毕。
class Solution { public: /* * @param nums: A list of integers. * @return: A list of permutations. */ vector<vector<int>> permute(vector<int> &nums) { // write your code here vector<vector<int>> result; if (nums.empty()) { result.push_back(nums); //此句不可少,否则输入[]时输出[],要求输出[[]]……; return result; } vector<int> s; s.push_back(nums[0]); result.push_back(s); //第一个元素; for (int i=1;i<(int)nums.size();i++) { int size1=result.size(); for (int j=0;j<size1;j++) { int size2=result[0].size(); for (int k=0;k<=size2;k++) { vector<int> temp=result[0];//每次都以第一个数组作为临时数组新增元素,增添完毕后要删除该数组; temp.insert(temp.begin()+k,nums[i]); result.push_back(temp); //结果数组更新是插在尾部,没有处理之前的临时数组,所以循环结束后应删掉第一个临时数组(临时数组使用完成); } result.erase(result.begin()); } } return result; } };
补充:temp.insert(temp.begin()+k,nums[i]); —————— 在数组下标k处插入nums[i],insert新增元素不会覆盖原有元素。
法3.字典排序 此法能适应有重复元素的排列
要求初始数组是升序的。从最小字典序开始,一直到最大字典序。如果初始非升序(不是字典序最小值),将漏掉比初始数组小的字典序排列。
AC代码:
class Solution { public: /* * @param : A list of integers * @return: A list of unique permutations */ vector<vector<int>> permuteUnique(vector<int> &nums) { // write your code here vector<vector<int>> result; if (nums.empty()) { result.push_back(nums); //此句不可少,否则输入[]时输出[],要求输出[[]]……; return result; } sort(nums.begin(),nums.end()); result.push_back(nums); while(1) { int p,k; for (int i=nums.size()-1;i>=0;i--) { if (i<=0)//说明nums为字典序最大值; { return result; } if (nums[i-1]<nums[i]) { p=i-1; break; } } for (int j=nums.size()-1;j>p;j--) { if (nums[j]>nums[p]) { k=j; break; } } swap(nums[k],nums[p]); reverse(nums.begin()+p+1,nums.end()); result.push_back(nums); } return result; } };