回溯算法练习day.5

时间:2024-04-24 22:44:24

491.非递减子序列

链接:. - 力扣(LeetCode)

题目描述:

给你一个整数数组 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.全排列

链接:. - 力扣(LeetCode)

题目描述:

给定一个不含重复数字的数组 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

链接:. - 力扣(LeetCode)

题目描述:

给定一个可包含重复数字的序列 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; // 返回结果数组
}