力扣39题、40题(求组合总和、回溯、递归)

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

39.给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。 对于给定的输入,保证和为 target 的唯一组合数少于 150 个。

  1. 看到题目想着,这跟17题、216题求组合不是一个样的题吗?于是一顿操作写出如下代码,然后运行是可以运行了,但是问题来了,由于题目说集合的每个数字都可以重复使用,所以我将每个for循环的下标都从0开始,这造成了什么情况?找到的结果是满足sum = target的排列,为了防止出现这种情况,还是需要限制for循环的起始下标,需要设置一个整型变量startIndex。这里就有一个这样的规则:在求组合的问题中,如果是在一个集合内求组合就用startIndex,如果是在多个集合内求组合就不用startIndex。
/**
 * 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* candidates, int candidatesSize, int target, int** res, int* returnSize, int* columnSizes, int* path, int pathSize, int sum, int startIndex) {
    if (sum > target) 
        return;
    if (sum == target) {
        res[*returnSize] = malloc(sizeof(int) * pathSize);
        for (int i = 0; i < pathSize; i++) {
            res[*returnSize][i] = path[i];
        }
        columnSizes[(*returnSize)++] = pathSize;
    }
    for (int i = 0; i < candidatesSize; i++) {
        path[pathSize++] = candidates[i];
        sum += candidates[i];
        backtracking(candidates, candidatesSize, target, res, returnSize, columnSizes, path, pathSize, sum);
        pathSize--; // 回溯
        sum -= candidates[i]; // 回溯
    }
}

int** combinationSum(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes){
    *returnSize = 0;
    int** res = malloc(sizeof(int *) * 150);
    int* columnSizes = malloc(sizeof(int) * 150);
    int* path = malloc(sizeof(int) * 2000);
    backtracking(candidates, candidatesSize, target, res, returnSize, columnSizes, path, 0, 0);
    *returnColumnSizes = malloc(sizeof(int) * (*returnSize));
    for (int i = 0; i < *returnSize; i++) {
        (*returnColumnSizes)[i] = columnSizes[i];
    }
    return res;
}

修改后的代码是这样的:

/**
 * 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* candidates, int candidatesSize, int target, int** res, int* returnSize, int* columnSizes, int* path, int pathSize, int sum, int startIndex) {
    if (sum > target) 
        return;
    if (sum == target) {
        res[*returnSize] = malloc(sizeof(int) * pathSize);
        for (int i = 0; i < pathSize; i++) {
            res[*returnSize][i] = path[i];
        }
        columnSizes[(*returnSize)++] = pathSize;
    }
    for (int i = startIndex; i < candidatesSize; i++) {
        path[pathSize++] = candidates[i];
        sum += candidates[i];
        backtracking(candidates, candidatesSize, target, res, returnSize, columnSizes, path, pathSize, sum, i); // 注意这里的startIndex的取值仍然是i,区别于17题中数字不能重复使用,startIndex要取i+1
        pathSize--;
        sum -= candidates[i];
    }
}

int** combinationSum(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes){
    *returnSize = 0;
    int** res = malloc(sizeof(int *) * 150); //结果数组
    int* columnSizes = malloc(sizeof(int) * 150); //用来存每个组合包含多少的数字
    int* path = malloc(sizeof(int) * 2000); //用来暂存组合
    backtracking(candidates, candidatesSize, target, res, returnSize, columnSizes, path, 0, 0, 0);
    *returnColumnSizes = malloc(sizeof(int) * (*returnSize));
    for (int i = 0; i < *returnSize; i++) {
        (*returnColumnSizes)[i] = columnSizes[i];
    }
    return res;
}
  1. 这里回溯的参数sum可以省略一下,每次用target减去取到的数字即可,当target等于0时则说明找到要找的组合了。
/**
 * 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* candidates, int candidatesSize, int target, int** res, int* returnSize, int* columnSizes, int* path, int pathSize, int startIndex) {
    if (target < 0) 
        return;
    if (target == 0) {
        res[*returnSize] = malloc(sizeof(int) * pathSize);
        for (int i = 0; i < pathSize; i++) {
            res[*returnSize][i] = path[i];
        }
        columnSizes[(*returnSize)++] = pathSize;
    }
    for (int i = startIndex; i < candidatesSize; i++) {
        path[pathSize++] = candidates[i];
        target -= candidates[i];
        backtracking(candidates, candidatesSize, target, res, returnSize, columnSizes, path, pathSize, i);
        pathSize--;
        target += candidates[i];
    }
}

int** combinationSum(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes){
    *returnSize = 0;
    int** res = malloc(sizeof(int *) * 150); //结果数组
    int* columnSizes = malloc(sizeof(int) * 150); //用来存每个组合包含多少的数字
    int* path = malloc(sizeof(int) * 2000); //用来暂存组合
    backtracking(candidates, candidatesSize, target, res, returnSize, columnSizes, path, 0, 0);
    *returnColumnSizes = malloc(sizeof(int) * (*returnSize));
    for (int i = 0; i < *returnSize; i++) {
        (*returnColumnSizes)[i] = columnSizes[i];
    }
    return res;
}
  1. 对上述代码仍然可以进行进一步优化,可以发现我们是在递归函数的开头发现当前找到的组合不满足要求,然后结束这层递归。但是如果我们在for循环处,也就是在进入递归之前提前判断这组组合是否会不符合要求,那就可以省去一步递归了;换句话,只让可能符合要求的组合进入for循环,从而进入递归函数,这样可以省去不少次递归。

虽然感觉逻辑上没有问题,但是在提交上却出了错,不知道为什么。

/**
 * 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* candidates, int candidatesSize, int target, int** res, int* returnSize, int* columnSizes, int* path, int pathSize, int startIndex) {
    if (target < 0) 
        return;
    if (target == 0) {
        res[*returnSize] = malloc(sizeof(int) * pathSize);
        for (int i = 0; i < pathSize; i++) {
            res[*returnSize][i] = path[i];
        }
        columnSizes[(*returnSize)++] = pathSize;
    }
    for (int i = startIndex; i < candidatesSize && target - candidates[i] >= 0 ; i++) {
        path[pathSize++] = candidates[i];
        target -= candidates[i];
        backtracking(candidates, candidatesSize, target, res, returnSize, columnSizes, path, pathSize, i);
        pathSize--;
        target += candidates[i];
    }
}

int** combinationSum(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes){
    *returnSize = 0;
    int** res = malloc(sizeof(int *) * 150); //结果数组
    int* columnSizes = malloc(sizeof(int) * 150); //用来存每个组合包含多少的数字
    int* path = malloc(sizeof(int) * 2000); //用来暂存组合
    backtracking(candidates, candidatesSize, target, res, returnSize, columnSizes, path, 0, 0);
    *returnColumnSizes = malloc(sizeof(int) * (*returnSize));
    for (int i = 0; i < *returnSize; i++) {
        (*returnColumnSizes)[i] = columnSizes[i];
    }
    return res;
}

这样就可以,很奇怪。

/**
 * 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* candidates, int candidatesSize, int target, int** res, int* returnSize, int* columnSizes, int* path, int pathSize, int startIndex) {
    if (target == 0) {
        res[*returnSize] = malloc(sizeof(int) * pathSize);
        for (int i = 0; i < pathSize; i++) {
            res[*returnSize][i] = path[i];
        }
        columnSizes[(*returnSize)++] = pathSize;
    }
    for (int i = startIndex; i < candidatesSize ; i++) {
        path[pathSize++] = candidates[i];
        target -= candidates[i];
        if (target >= 0)
            backtracking(candidates, candidatesSize, target, res, returnSize, columnSizes, path, pathSize, i);
        pathSize--;
        target += candidates[i];
    }
}

int** combinationSum(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes){
    *returnSize = 0;
    int** res = malloc(sizeof(int *) * 150); //结果数组
    int* columnSizes = malloc(sizeof(int) * 150); //用来存每个组合包含多少的数字
    int* path = malloc(sizeof(int) * 2000); //用来暂存组合
    backtracking(candidates, candidatesSize, target, res, returnSize, columnSizes, path, 0, 0);
    *returnColumnSizes = malloc(sizeof(int) * (*returnSize));
    for (int i = 0; i < *returnSize; i++) {
        (*returnColumnSizes)[i] = columnSizes[i];
    }
    return res;
}

40.给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。
注意:解集不能包含重复的组合。

  1. 40题跟39题的区别在于,39题中的集合中的每个数字都是唯一的,且一个数字可以重复使用;而40题中的集合的每个数字不是唯一的,且一个数字不能重复使用;前面39题由于每个数字是唯一的,因此通过设置startIndex就可以使得不出现重复的组合,但是40题如果用同样的方式去解的话,由于集合中每个数字不是唯一的,所以仍然可能会出现重复的组合,这就令人头疼了。
  2. 将找组合的问题抽象为树形结构,则for循环是横向遍历,递归是纵向遍历。由于组合中的元素可以存在重复,所以纵向就不用去重了,我们要处理的去重是在横向上去重,在横向上所使用过的元素就不允许再次使用了,因为在第一次使用的时候已经把可能的组合都找出来了,再次使用的话只会导致重复组合的出现。这里定义了一个布尔类型的数组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* candidates, int candidatesSize, int target, int** res, int* returnSize, int* columnSizes, int* path, int pathSize, int startIndex, bool* used) {
    if (target == 0) { //找到满足的组合,就把path数组中暂存的值放到结果数组中
        res[*returnSize] = malloc(sizeof(int) * pathSize);
        for (int i = 0; i < pathSize; i++) {
            res[*returnSize][i] = path[i];
        }
        columnSizes[(*returnSize)++] = pathSize;
    }
    for (int i = startIndex; i < candidatesSize ; i++) {
    	// 若当前使用的元素跟上一次使用的元素相同的话,则看used[i-1],used[i-1]为false的话代表是横向出现了重复的元素,要去重,所以直接跳过此次循环
        if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) 
            continue;
        
        path[pathSize++] = candidates[i];
        target -= candidates[i];
        used[i] = true; // 进入递归前我们先让used[i]置为true,这样的话在下一层递归的时候即使遇到了重复的元素,也可以继续往下执行,因为纵向不需要去重
        if (target >= 0) // 如果target < 0的话就没必要进入递归了
            backtracking(candidates, candidatesSize, target, res, returnSize, columnSizes, path, pathSize, i + 1, used);
        used[i] = false; //回溯
        target += candidates[i]; //回溯 
        pathSize--; //回溯
    }
}

// 比较函数,用于作为下面qsort函数的参数
int cmp_int(const void* _a , const void* _b) {
    int* a = (int*)_a;    //强制类型转换
    int* b = (int*)_b;
    return *a - *b;
}

int** combinationSum2(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes){
    *returnSize = 0;
    int** res = malloc(sizeof(int *) * 150); //结果数组
    int* columnSizes = malloc(sizeof(int) * 150); //用来存每个组合包含多少的数字
    int* path = malloc(sizeof(int) * 2000); //用来暂存组合
    bool* used = malloc(sizeof(bool) * candidatesSize); //用于判断是横向出现了重复的元素还是纵向出现了重复的元素
    for (int i = 0; i < candidatesSize; i++) { // used数组的元素值都初始化为false
        used[i] = false;
    }
    qsort(candidates, candidatesSize, sizeof(int), cmp_int); //快排函数,让给定的数字序列按顺序排列
    backtracking(candidates, candidatesSize, target, res, returnSize, columnSizes, path, 0, 0, used);
    *returnColumnSizes = malloc(sizeof(int) * (*returnSize));
    for (int i = 0; i < *returnSize; i++) {
        (*returnColumnSizes)[i] = columnSizes[i];
    }
    return res;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值