46.给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
- 求排列问题跟求组合问题和分割子串问题最大的不同在于,求组合问题和分割子串问题都需要定义一个整型变量startIndex去控制对数组和字符串的遍历范围(求不同集合之间的组合除外),而求排列问题不需要设置startIndex去控制对数组的遍历范围,每次遍历都需要从数组头开始遍历,即就算之前的数被使用过,后面仍然需要被使用。举例来说,就是元素1在获得排列[1,2]的过程中已经使用过,但是后面再获得排列[2,1]的使用仍然要使用到元素1;而如果是求组合问题,元素1在获得组合[1,2]的时候用过,后面在寻找以2开头的组合的时候,1元素就不会再被使用。
- 由于每次从数组中选取元素的过程都是从数组头开始,但是数组中的每个元素在一个排列中只能使用一次,因此这里定义了一个used数组,来记录每个元素是否被使用过,被使用过的元素就不能被再次使用了。
- 代码如下:
/**
* Return an array of arrays of size *returnSize.
* The sizes of the arrays are returned as *returnColumnSizes array.
* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
*/
void backtracking(int* nums, int numsSize, int** res, int* returnSize, int* path, int pathSize, bool* used) {
if (pathSize == numsSize) { //找到一个排列
res[*returnSize] = malloc(sizeof(int) * pathSize);
for (int i = 0; i < pathSize; i++) {
res[*returnSize][i] = path[i];
}
(*returnSize)++;
return;
}
for (int i = 0; i < numsSize; i++) { //每次都从数组头开始遍历
if (used[i] == true) //如果当前元素被使用过,则跳过这次循环
continue;
used[i] = true;
path[pathSize++] = nums[i];
backtracking(nums, numsSize, res, returnSize, path, pathSize, used);
//回溯
pathSize--;
used[i] = false;
}
}
int factorial(int num) { //求阶乘
if (num == 1 || num == 0)
return 1;
else
return num * factorial(num - 1);
}
int** permute(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){
*returnSize = 0;
int** res = malloc(sizeof(int *) * factorial(numsSize));
int* path = malloc(sizeof(int) * numsSize); //暂存组合元素
*returnColumnSizes = malloc(sizeof(int) * factorial(numsSize));
bool* used = malloc(sizeof(bool) * numsSize); //用于记录数组中的元素是否被使用过
for (int i = 0; i < numsSize; i++) {
used[i] = false;
}
backtracking(nums, numsSize, res, returnSize, path, 0, used);
for (int i = 0; i < *returnSize; i++) {
(*returnColumnSizes)[i] = numsSize;
}
return res;
}
47.给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
- 47题跟46题的区别在于47题给定的序列是包含重复数字的,那么如果按上面的代码来求排列的话就可能会出现重复的组合,所以这里要去重。这里的去重手段跟40题求组合时的去重手段是一样的,将序列排序,让相同的数都排在相邻的位置,然后定义一个布尔类型的数组used来记录各个元素的使用情况。这里的used数组有两个作用,第一个作用是记录当前取出来的元素是否使用过,因为求排列时序列中的每个元素只能被使用一次,并且每次遍历都是从数组头开始遍历的,因此可能当前取出来的元素是已经使用过的,如果取出来的元素是使用过的,则跳过当前次的循环;第二个作用就是去重了,如果当前取出来的元素nums[i]和上一次取出来的元素nums[i-1]是一样的,并且上一次取出来的元素的下标在used数组中对应的元素为false的话,即used[i - 1] = false,则说明是在横向遍历的时候出现了重复元素,这种情况就是我们要去重的情况,则跳过当前次循环。
/**
* Return an array of arrays of size *returnSize.
* The sizes of the arrays are returned as *returnColumnSizes array.
* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
*/
void backtracking(int* nums, int numsSize, int** res, int* returnSize, int* path, int pathSize, bool* used) {
if (pathSize == numsSize) { //找到一个排列
res[*returnSize] = malloc(sizeof(int) * pathSize);
for (int i = 0; i < pathSize; i++) {
res[*returnSize][i] = path[i];
}
(*returnSize)++;
return;
}
for (int i = 0; i < numsSize; i++) {
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) //横向出现重复的元素,要去重
continue;
if (used[i] == true) //控制序列中每个元素在每个排列中只使用一次
continue;
used[i] = true;
path[pathSize++] = nums[i];
backtracking(nums, numsSize, res, returnSize, path, pathSize, used);
// 回溯
pathSize--;
used[i] = false;
}
}
//比较函数,作为qsort函数的参数
int cmp_int(const void* _a , const void* _b) {
int* a = (int*)_a; //强制类型转换
int* b = (int*)_b;
return *a - *b;
}
int factorial(int num) { //求阶乘
if (num == 1 || num == 0)
return 1;
else
return num * factorial(num - 1);
}
int** permuteUnique(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){
*returnSize = 0;
int** res = malloc(sizeof(int*) * factorial(numsSize)); // 结果最多有numsSize!个
int* path = malloc(sizeof(int) * numsSize);
bool* used = malloc(sizeof(bool) * numsSize); // 记录每个元素的使用情况,用于控制序列中每个元素在每个排列中只使用一次,还用于去重
for (int i = 0; i < numsSize; i++) {
used[i] = false;
}
qsort(nums, numsSize, sizeof(int), cmp_int); //将给定序列排序
backtracking(nums, numsSize, res, returnSize, path, 0, used);
*returnColumnSizes = malloc(sizeof(int) * (*returnSize));
for (int i = 0; i < *returnSize; i++) {
(*returnColumnSizes)[i] = numsSize;
}
return res;
}