力扣77题、216题(求组合、回溯、递归)

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

77.给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。你可以按 任何顺序 返回答案。

  1. 说到回溯那就离不开递归,回溯就是递归中的一种现象, 某些传递给递归函数的参数,在递归结束的时候,这些参数又会变成进入递归函数之前的样子,利用这种特性就可以用来解决一些穷举问题。比如该题,返回k个数的组合,如果不使用递归的话,那么当就需要有k个for循环嵌套,这样根本写不出来。但是如果利用递归的话,每个递归中设置一个for循环,那么就可以利用递归来实现很多个for循环嵌套了,利用的方法就是在for循环中递归调用自己,这样就能够实现多个for循环嵌套。并且要注意,一个递归函数结束时需要回溯某些传给递归函数的变量。
  2. 该题的代码如下,回溯的递归函数一般没有返回值,参数的确定要看实际情况。
/**
 * 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 n, int k, int* path, int pathSize, int** res, int* returnSize, int* columnSizes, int startIndex) {
	// 当path数组存到k个数的时候,即找到了一个组合,递归结束,将path数组中的元素放到结果数组中
    if (pathSize == k) {
        res[*returnSize] = malloc(sizeof(int) * pathSize);
        for (int i = 0; i < pathSize; i++) {
            res[*returnSize][i] = path[i];
        }
        columnSizes[(*returnSize)++] = pathSize;
        return;
    }
    for (int i = startIndex; i <= n; i++) {
        path[pathSize++] = i;
        backtracking(n, k, path, pathSize, res, returnSize, columnSizes, i + 1);
        pathSize--; //回溯
    }
}

int** combine(int n, int k, int* returnSize, int** returnColumnSizes){
    // 结果数组
    int** res = malloc(sizeof(int *) * 10000);
    // 将回溯问题抽象成树形结构,则结果数组中的每一个元素其实是树中的一条路径,path数组用来暂存找到的一个组合
    int* path = malloc(sizeof(int) * 10000);
    int* columnSizes = malloc(sizeof(int) * 10000);
    *returnSize = 0;
    backtracking(n, k, path, 0, res, returnSize, columnSizes, 1);
    (*returnColumnSizes) = malloc(sizeof(int) * (*returnSize));
    for (int i = 0; i < *returnSize; i++) {
        (*returnColumnSizes)[i] = columnSizes[i];
    }
    return res;
}
  1. 上述代码中,for循环的遍历范围其实是可以优化的, 想象一下,如果当前for循环的范围包含的元素个数小于我们所需要的元素个数,那么这个for循环就没有执行下去的必要了,所以我们可以把for循环的遍历范围缩小一下,只执行那些有必要执行的for循环。那么如何缩小呢?由于目前已经选择的元素个数为pathSize,那么我们还需要的元素个数是k - pathSize,则for循环至多可以从 n - (k- pathSize) + 1处开始遍历,这里因为是闭区间所以要+1,否则会少一种情况,可以举简单例子看看。于是上述代码中for循环改为如下代码:
    for (int i = startIndex; i <= n - (k - pathSize) + 1; i++) {
        path[pathSize++] = i;
        backtracking(n, k, path, pathSize, res, returnSize, columnSizes, i + 1);
        pathSize--;
    }

216.找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:
所有数字都是正整数。
解集不能包含重复的组合。

  1. 该题跟77题基本是一样的,只是加了一个限制:相加之和为n,所以对上面的代码进行改写,不难有下面的代码:
/**
 * 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 k, int n, int* path, int pathSize, int** res, int* returnSize, int* columnSizes, int startIndex) {
    if (pathSize == k) {
        int sum = 0;
        for (int i = 0; i < pathSize; i++) {
            sum += path[i];
        }
        if (sum == n) {
            res[*returnSize] = malloc(sizeof(int) * pathSize);
            for (int i = 0; i < pathSize; i++) {
                res[*returnSize][i] = path[i];
            }
            columnSizes[(*returnSize)++] = pathSize;
        }
        return;
    }

    for (int i = startIndex; i <= 9 - (k - pathSize) + 1; i++) {
        path[pathSize++] = i;
        backtracking(k, n, path, pathSize, res, returnSize, columnSizes, i + 1);
        pathSize--;
    }
}

int** combinationSum3(int k, int n, int* returnSize, int** returnColumnSizes){
    int** res = malloc(sizeof(int *) * 1000);
    *returnSize = 0;
    int* path = malloc(sizeof(int) * 9);
    int* columnSizes = malloc(sizeof(int) * 1000);
    backtracking(k, n, path, 0, res, returnSize, columnSizes, 1);
    *returnColumnSizes = malloc(sizeof(int) * (*returnSize));
    for (int i = 0; i < *returnSize; i++) {
        (*returnColumnSizes)[i] = columnSizes[i];
    }
    return res;
}
  1. 虽然上述代码已经在for循环的遍历范围上进行了缩小,但是还能进一步对代码进行优化。可以看到上述代码在pathSize == k的时候都要用一个for循环取出path的所有元素进行相加来求和,如果将sum作为递归函数的参数的话,就可以在一边增加path数组中元素一边求path数组的和。
/**
 * 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 k, int n, int* path, int pathSize, int sum, int** res, int* returnSize, int* columnSizes, int startIndex) {
    if (pathSize == k) {
        if (sum == n) {
            res[*returnSize] = malloc(sizeof(int) * pathSize);
            for (int i = 0; i < pathSize; i++) {
                res[*returnSize][i] = path[i];
            }
            columnSizes[(*returnSize)++] = pathSize;
        }
        return;
    }

    for (int i = startIndex; i <= 9 - (k - pathSize) + 1; i++) {
        path[pathSize++] = i;
        sum += i;
        backtracking(k, n, path, pathSize, sum, res, returnSize, columnSizes, i + 1);
        pathSize--; // 回溯
        sum -= i; //回溯
    }
}

int** combinationSum3(int k, int n, int* returnSize, int** returnColumnSizes){
    int** res = malloc(sizeof(int *) * 1000);
    *returnSize = 0;
    int* path = malloc(sizeof(int) * 9);
    int* columnSizes = malloc(sizeof(int) * 1000);
    backtracking(k, n, path, 0, 0, res, returnSize, columnSizes, 1);
    *returnColumnSizes = malloc(sizeof(int) * (*returnSize));
    for (int i = 0; i < *returnSize; i++) {
        (*returnColumnSizes)[i] = columnSizes[i];
    }
    return res;
}
  1. 在上述优化的基础上,还可以再进一步优化。思考一下,在进入到某层递归之后,假如path数组中的元素之和已经超过所要求的和n的话,那么就没有必要再递归下去了,直接结束当前的递归函数,这样可以省去很多不符合要求的情况。所以最终的优化结果如下。
/**
 * 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 k, int n, int* path, int pathSize, int sum, int** res, int* returnSize, int* columnSizes, int startIndex) {
    if (sum > n)
        return;
    if (pathSize == k) {
        if (sum == n) {
            res[*returnSize] = malloc(sizeof(int) * pathSize);
            for (int i = 0; i < pathSize; i++) {
                res[*returnSize][i] = path[i];
            }
            columnSizes[(*returnSize)++] = pathSize;
        }
        return;
    }

    for (int i = startIndex; i <= 9 - (k - pathSize) + 1; i++) {
        path[pathSize++] = i;
        sum += i;
        backtracking(k, n, path, pathSize, sum, res, returnSize, columnSizes, i + 1);
        pathSize--; // 回溯
        sum -= i; //回溯
    }
}

int** combinationSum3(int k, int n, int* returnSize, int** returnColumnSizes){
    int** res = malloc(sizeof(int *) * 1000);
    *returnSize = 0;
    int* path = malloc(sizeof(int) * 9);
    int* columnSizes = malloc(sizeof(int) * 1000);
    backtracking(k, n, path, 0, 0, res, returnSize, columnSizes, 1);
    *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、付费专栏及课程。

余额充值