491.非递减子序列
题目描述:
给你一个整数数组
nums
,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
示例 1:
输入:nums = [4,6,7,7] 输出:[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]]示例 2:
输入:nums = [4,4,3,2,1] 输出:[[4,4]]提示:
1 <= nums.length <= 15
-100 <= nums[i] <= 100
思路:
因为是求组合问题,因此使用回溯算法,就可以抽象出树形结构,如下所示
由这个树形结构可以看出,对于我们同一层的排列,是不能取数值相同的元素的,即图中绿色标出的部分,否则会导致重复,而对于同一个向下的支路,因为他们是不会导致重复,因此可以取得值相同的元素,根据题目给出的例子,我们可以知道,不能先对例子进行排列,否则输出的结果不准确,并且在这题中,也是在节点收集结果,而不仅仅是叶子节点,我们只是要求节点中元素个数大于二且非递减
回溯实现:
1.确定参数和返回值,参数就是题目的集合已经每次开始遍历的位置
2.确定函数终止条件,当遍历到集合末尾时,退出
3.确定单层递归逻辑,使用一个辅助数组来标记每一层的循环中出现过的元素,如果递归中不是非递减的或者重复出现,则继续访问另外的支路,否则收集结果,递归,回溯
代码实现:
int* path; // 定义路径数组指针 int pathTop; // 定义路径数组的顶部指针 int** ans; // 定义结果数组的指针 int ansTop; // 定义结果数组的顶部指针 int* length; // 定义长度数组指针,用于存储每个子序列的长度 // 将当前路径内容复制到结果数组中 void copy() { int* tempPath = (int*)malloc(sizeof(int) * pathTop); // 分配临时数组以存储路径内容 memcpy(tempPath, path, pathTop * sizeof(int)); // 将路径内容复制到临时数组中 length[ansTop] = pathTop; // 存储当前路径的长度到长度数组中 ans[ansTop++] = tempPath; // 将临时数组添加到结果数组中,并更新结果数组的顶部指针 } // 在给定数组中查找是否存在特定值 int find(int* uset, int usetSize, int key) { int i; for(i = 0; i < usetSize; i++) { if(uset[i] == key) // 如果找到目标值 return 1; // 返回1表示找到 } return 0; // 否则返回0表示未找到 } // 回溯算法的实现 void backTracking(int* nums, int numsSize, int startIndex) { // 当路径中的元素个数大于1时,将当前路径复制到结果数组中 if(pathTop > 1) { copy(); } int* uset = (int*)malloc(sizeof(int) * numsSize); // 分配临时数组用于存储已经访问过的元素 int usetTop = 0; // 初始化临时数组的顶部指针为0 int i; for(i = startIndex; i < numsSize; i++) { // 如果当前元素小于路径中的最后一个元素,或者在同一层次的树中找到相同的元素,则跳过当前元素 if((pathTop > 0 && nums[i] < path[pathTop - 1]) || find(uset, usetTop, nums[i])) continue; // 将当前元素添加到临时数组中 uset[usetTop++] = nums[i]; // 将当前元素添加到路径中 path[pathTop++] = nums[i]; backTracking(nums, numsSize, i + 1); // 递归调用,继续向下探索 // 回溯操作,将路径顶部指针减1,恢复到上一层状态 pathTop--; } } // 寻找数组的所有递增子序列 int** findSubsequences(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){ // 初始化路径数组、结果数组、长度数组以及相关指针 path = (int*)malloc(sizeof(int) * numsSize); ans = (int**)malloc(sizeof(int*) * 33000); length = (int*)malloc(sizeof(int) * 33000); pathTop = ansTop = 0; backTracking(nums, numsSize, 0); // 调用回溯算法寻找递增子序列 // 设置返回的结果数组的大小以及每个一维数组的长度 *returnSize = ansTop; *returnColumnSizes = (int*)malloc(sizeof(int) * ansTop); int i; for(i = 0; i < ansTop; i++) { (*returnColumnSizes)[i] = length[i]; // 将长度数组的值赋给返回的一维数组长度 } return ans; // 返回结果数组 }
46.全排列
题目描述:
给定一个不含重复数字的数组
nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。示例 1:
输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]示例 2:
输入:nums = [0,1] 输出:[[0,1],[1,0]]示例 3:
输入:nums = [1] 输出:[[1]]提示:
1 <= nums.length <= 6
-10 <= nums[i] <= 10
nums
中的所有整数 互不相同
思路:
因为是排列问题,因此使用回溯算法来进行解决,我们可以将其抽象为一颗树形结构,并使用一个辅助数组来标记我们每一条支路中使用过的元素
根据树形结构,我们就可以知道每个叶子节点就是我们需要的结果,因为这里求的是排列而不是组合,因此,在不同的大支路中是可以取到前面使用过的元素的,例如第一条支路取了1,第二条支路取2时,在它的分支下还是取到1,因此可以根据树形结构写出实现思路
回溯实现
1.确定函数参数和返回值,参数为题目的集合已经使用的标记数组
2.确定终止条件,到达叶子节点则进行收集,即单个结果集的大小与题目给的集合大小一样时,进行结果的收集
3.确定单层递归逻辑,判断元素是否被收集,如果有则进行下一个判断,如果没有则进行收集,递归,回溯
代码实现:
/** * 返回一个大小为*returnSize的数组的数组。 * 数组的大小作为*returnColumnSizes数组返回。 * 注意:返回的数组和*columnSizes数组必须分配内存,假设调用者会调用free()释放内存。 */ int *path; // 声明一个路径数组,用于存储当前遍历的路径 int pathtop; // 记录路径数组中元素的个数 int **result; // 声明一个结果数组的数组,用于存储所有的排列组合 int resulttop; // 记录结果数组中的数组个数 // 设置数组中所有元素为0 void set_0(int *used, int usedtop) { for(int i = 0; i < usedtop; i++) used[i] = 0; } // 复制当前路径到结果数组中 void copy() { int *temp = (int *)malloc(sizeof(int ) * pathtop); for(int i = 0; i < pathtop ; i++) temp[i] = path[i]; result[resulttop++] = temp; } // 回溯法求解排列组合 void backtacking(int *nums, int numsSize, int *used) { if(pathtop == numsSize) // 如果路径长度等于数组大小,将当前路径复制到结果数组中 copy(); for(int i = 0; i < numsSize; i++) { if(used[i]) // 如果当前元素已经被使用过,则跳过 continue; used[i] = 1; // 标记当前元素为已使用 path[pathtop++] = nums[i]; // 将当前元素添加到路径中 backtacking(nums,numsSize,used); // 递归求解下一个元素的排列组合 pathtop--; // 回溯,恢复到上一个状态 used[i] = 0; // 取消当前元素的使用标记 } } // 求解排列组合的入口函数 int** permute(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) { path = (int*)malloc(sizeof(int) * numsSize); // 为路径数组分配内存空间 result = (int**)malloc(sizeof(int*) * 1000); // 为结果数组的数组分配内存空间,初始大小设置为1000 int* used = (int*)malloc(sizeof(int) * numsSize); // 为标记数组分配内存空间 set_0(used,numsSize); // 将标记数组中的所有元素设置为0 resulttop = pathtop = 0; // 初始化结果数组中数组个数和路径数组长度 backtacking(nums, numsSize,used); // 调用回溯函数求解排列组合 *returnSize = resulttop; // 将结果数组中数组的个数返回 *returnColumnSizes = (int*)malloc(sizeof(int) * resulttop); // 为返回的列大小数组分配内存空间 int i; for(i = 0; i < resulttop; i++) { (*returnColumnSizes)[i] = numsSize; // 设置每个列的大小为数组的大小 } return result; // 返回排列组合的结果数组 }
47.全排列II
题目描述:
给定一个可包含重复数字的序列
nums
,按任意顺序 返回所有不重复的全排列。示例 1:
输入:nums = [1,1,2] 输出: [[1,1,2], [1,2,1], [2,1,1]]示例 2:
输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]提示:
1 <= nums.length <= 8
-10 <= nums[i] <= 10
思路:
这题与上题一样都是全排列问题,但是题目给的是包含重复数字的序列 ,因为我们要进行去重操作,按照例子112来说,如果不进行去重,我们得出的结果就会包含两个112,这就导致输出的结果不准确,去重操作之前要进行排序操作,便于我们判断元素是否被使用,实现的思路与上题一样
代码实现:
int *path; // 定义一个数组指针path,用于存储当前的路径 int pathtop; // 记录当前路径的长度 int **result; // 定义一个指向指针的指针result,用于存储所有的排列结果 int resulttop; // 记录当前已生成排列的个数 int *used; // 定义一个数组指针used,用于标记元素是否被使用过 void copy() { int *temp = (int *)malloc(sizeof(int) * pathtop); // 分配内存以存储当前路径 for(int i = 0; i < pathtop; i++) // 复制当前路径到temp中 temp[i] = path[i]; result[resulttop++] = temp; // 将temp添加到result中,并更新resulttop } int cmp(const void *a, const void *b) { return *((int *)a) - *((int *)b); // 比较两个整数的大小,用于qsort排序 } void backtracking(int *nums, int numsSize, int *used) { if(pathtop == numsSize) // 如果当前路径长度等于数组大小,说明已经生成一个排列 { copy(); // 将当前路径复制到结果中 return; // 返回 } for(int i = 0; i < numsSize ; i++) // 遍历数组元素 { if(used[i] || (i > 0 && nums[i] == nums[i - 1] && !used[i - 1])) // 如果元素已经被使用过,或者当前元素与前一个相同且前一个未被使用过 continue; // 跳过当前元素 path[pathtop++] = nums[i]; // 将当前元素添加到路径中,并更新路径长度 used[i] = 1; // 标记当前元素为已使用 backtracking(nums, numsSize, used); // 递归调用backtracking函数 pathtop--; // 回溯,恢复路径长度 used[i] = 0; // 恢复当前元素的使用状态 } } int** permuteUnique(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) { qsort(nums, numsSize, sizeof(int), cmp); // 对数组进行排序,以便处理重复元素的情况 path = (int *)malloc(sizeof(int) * numsSize); // 分配内存以存储路径 result = (int **)malloc(sizeof(int*) * 1000); // 分配内存以存储结果 used = (int *)malloc(sizeof(int ) * numsSize); // 分配内存以存储元素使用情况 pathtop = resulttop = 0; // 初始化路径长度和结果计数器 for(int i = 0; i < numsSize ;i++) used[i] = 0; // 初始化元素使用情况 backtracking(nums, numsSize, used); // 调用backtracking函数生成排列 *returnSize = resulttop; // 返回结果的总数 *returnColumnSizes = (int *)malloc(sizeof(int) * resulttop); // 分配内存以存储每个排列的长度 for(int i = 0; i < resulttop; i++) (*returnColumnSizes)[i] = numsSize; // 设置每个排列的长度为数组大小 return result; // 返回结果数组 }