leadcode 题46
给定一个没有重复数字的序列,返回其所有可能的全排列。
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
初始的想法,对于无重复序列,所有的可能为n!。以初始序列中任意数字a[i]开头的序列有(n-1)!种,已经选择a[i]开头,再选择a[j]作为第二个数字一共有(n-2)!种。因此可以填出所有的组合:
1.已知一个n长不重复序列a,有n!种不同的排列;
2.遍历a中的每一个元素,每次将a[i]作为首位,剩下的元素组成新的n-1序列
3.直到新序列的长度为0时,该种排列结束,否则重复1,2
// code 1
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
int len = nums.size();
int lenlen = 1;
for(int i = 1; i < len+1; i++)
lenlen *= i;
vector<vector<int>> result(lenlen);
for (int i = 0; i < lenlen; i++)
result[i].resize(len);
sub_permute(result, nums, 0, 0);
return result;
}
void sub_permute(vector<vector<int>>& result, vector<int>& nums, int x, int y){
int len = nums.size();
int kinds = 1;
int itera = 1;
for (int i = 1; i < len; i++)
itera *= i;
kinds = itera * len;
for (int j = 0; j < nums.size(); j++){
for (int i = 0; i < itera; i++){
result[x+i + j*itera][y] = nums[j]; //j*itera 是行上的偏移
}
vector<int> new_num(nums);
new_num.erase(new_num.begin()+j);
sub_permute(result, new_num, x+j*itera, y+1);
}
}
};
临时写的代码,表意可能不太清晰。
如果题目去掉没有重复这个条件,在上述方法中做修改很不方便。
更为一般的写法:
思路是一致的,只不过这种方法没有开辟新的空间存储子序列,而是通过交换的方式,从首位开始逐步递归。(需要注意,交换之后还需要交换回来,回到交换前的状态)
代码来自Q-WHai
// code 2
#include<iostream>
using namespace std;
int arr[5]={0,1,2,3,4};
void swap(int x,int y)//用于交换数组的两个数
{
int temp=arr[x];
arr[x]=arr[y];
arr[y]=temp;
}
int resove(int n)//递归函数
{
if(n==5)//当尝试对不存在的数组元素进行递归时,标明所有数已经排列完成,输出。
{
for(int i=0;i<5;i++)
cout<<arr[i];
cout<<endl;
return 0;
}
for(int i=n;i<5;i++)//循环实现交换和之后的全排列
{//i是从n开始 i=n;swap(n,i)相当于固定当前位置,在进行下一位的排列。
swap(n,i);
resove(n+1);
swap(n,i);
}
}
int main()
{
resove(0);
}
这种思路便于处理有重复的问题,在每次交换i,j之前先检查在j之前是否有值与j相等的数,若有,则跳过此次交换。
// code 3
private static void core(int[] array) {
int length = array.length;
fullArray(array, 0, length - 1);
}
private static boolean swapAccepted(int[] array, int start, int end) {
for (int i = start; i < end; i++) {
if (array[i] == array[end]) { //end之前出现了和end相同的数字,跳过本次交换
return false;
}
}
return true;
}
private static void fullArray(int[] array, int cursor, int end) {
if (cursor == end) {
System.out.println(Arrays.toString(array));
}
else {
for (int i = cursor; i <= end; i++) {
if (!swapAccepted(array, cursor, i)) {
continue;
}
ArrayUtils.swap(array, cursor, i);
fullArray(array, cursor + 1, end);
ArrayUtils.swap(array, cursor, i); // 用于对之前交换过的数据进行还原
}
}
}
字典序
code1满足排列结果是字典序的(前提是先按字典序排好序列);
code2,3都不能满足。
方法:
1.从右端开时,找出第一个比右边数字小的数字序号j,j=max{i|a[i] < a[i+1]}
2.a[j]右边小于a[j]的最小的数字a[k],由a[j]的定义,其右边的数都是递减的,因此k=max{i|a[i] > a[j]}
3.交换a[j],a[k]
4.将a[j+1]...a[k]...a[n]逆序
在这里不做算法证明,只做一些解释性的理解:
当前序列情况:a[0] < ... < a[j] < a[j+1] > a[j+2] > ... > a[k] >... > a[n]。那么a[j+2] ... a[n]已经是最大字典序的子序列了,所以我们考虑再往前看一位,找到从右往左打破了递增关系的a[j]。将a[j]和恰恰大于它的a[k]交换,交换后,从a[j+1]到a[n]依然满足增序。将a[j+1] ... a[n]逆序,成为一个最小子序列。
以上的操作相当于在最高位上换上了一个更大一点的数字,因此此时的次高位到末位需要呈现一种最小的排列。即:
从 1 5 4 3 2 (1)
变为 2 5 4 3 1 (2)
最终 2 1 3 4 5 (3)
找到l了(1)的下一个序列(3)。
通过上述安字典序的方式,先排序,之后逐步寻找下一个排列,是一种解决非重复问题的非递归方法。