来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum-iii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
77.给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。你可以按 任何顺序 返回答案。
- 说到回溯那就离不开递归,回溯就是递归中的一种现象, 某些传递给递归函数的参数,在递归结束的时候,这些参数又会变成进入递归函数之前的样子,利用这种特性就可以用来解决一些穷举问题。比如该题,返回k个数的组合,如果不使用递归的话,那么当就需要有k个for循环嵌套,这样根本写不出来。但是如果利用递归的话,每个递归中设置一个for循环,那么就可以利用递归来实现很多个for循环嵌套了,利用的方法就是在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 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;
}
- 上述代码中,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 的正整数,并且每种组合中不存在重复的数字。
说明:
所有数字都是正整数。
解集不能包含重复的组合。
- 该题跟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;
}
- 虽然上述代码已经在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;
}
- 在上述优化的基础上,还可以再进一步优化。思考一下,在进入到某层递归之后,假如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;
}