代码随想录算法训练营第二十五天
- LeetCode 216.组合总和III
- 题目描述
- 思路
- 参考代码
- 总结
- LeetCode 17.电话号码的字母组合
- 题目描述
- 思路
- 参考代码
LeetCode 216.组合总和III
题目链接:216.组合总和III
文章讲解:代码随想录#216.组合总和III
视频讲解:回溯算法如何剪枝?| LeetCode:216.组合总和III
题目描述
找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:
- 只使用数字1到9
- 每个数字最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
示例1
输入:k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。
示例2
输入:k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
解释:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
没有其他符合的组合了。
提示
- 2 <= k <= 9
- 1 <= n <= 60
思路
在数字1到9之间找到k个数,其和为n,并且保证每个数字只能使用一次。
如果使用k个for循环遍历数字,明显不太合适,所以此题应使用回溯法来求解。
k表示递归的次数,也就是树的深度。
数字1到9表示每次遍历树的宽度,题中要求每个数字只使用一次,所以每次递归时需要考虑遍历1到9的索引。
回溯的终止条条件为数的和为n。
在遍历的过种中也可以考虑如下的剪枝操作:
①当数和已经大于n了,退出当前循环。
②当剩余的数字少于还需要的数字个数时,for循环结束。
参考代码
int sum, cnt;
int** res;
typedef struct {
int index;
int nums[10];
} Data;
Data data = {0};
void backtracking(int k, int n, int idx) {
if (data.index == k) {
if (sum == n) { // 符合条件,记录数据
res[cnt] = malloc(k * sizeof(int));
for (int i = 0; i < k; i++)
res[cnt][i] = data.nums[i];
cnt++;
}
return;
}
// 剪枝二:保证剩余数字个数大于等于还需要的数字个数
for (int i = idx; i <= 9 - (k - data.index) + 1; i++) {
if (sum + i > n)
break; // 剪枝一:如果sum+i大于n,直接跳出for循环
sum += i;
data.nums[data.index++] = i;
backtracking(k, n, i + 1);
sum -= i; // 回溯
data.index--;
data.nums[data.index] = 0;
}
}
int** combinationSum3(int k, int n, int* returnSize, int** returnColumnSizes) {
res = (int**)malloc(100 * sizeof(int*));
sum = 0;
cnt = 0;
backtracking(k, n, 1);
*returnSize = cnt;
*returnColumnSizes =
(int*)malloc(sizeof(int) * cnt); // 需要给returnColumnSizes分配内存
for (int i = 0; i < cnt; i++) {
(*returnColumnSizes)[i] = k;
}
return res;
}
总结
- 这道题相对 LeetCode 77.组合 多了一个条件,需要找出和为n的k个数的组合,整个集合是固定的,相对来说也比较简单。
LeetCode 17.电话号码的字母组合
题目链接:17.电话号码的字母组合
文章讲解:代码随想录#17.电话号码的字母组合
视频讲解:还得用回溯算法!| LeetCode:17.电话号码的字母组合
题目描述
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例1
输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]
示例2
输入:digits = “”
输出:[]
示例3
输入:digits = “2”
输出:[“a”,“b”,“c”]
提示
- 0 <= digits.length <= 4
- digits[i] 是范围 [‘2’, ‘9’] 的一个数字。
思路
这道题关键是需要构建一个数字与字母转换的表,如:
[2] = [“abc”]
[3] = [“def”]
…
对于示例1来说,输入了两个数字2和3,树的深度为2。
首先遍历数字2,从转换表中取出字符串"abc",
然后先遍历a,将a存起来,(第一层)
调用递归函数,在递归函数中,遍历数字3…(第二层)
退出递归函数后,进行回溯操作,
接着再遍历b,将b存起来,
…
直到遍历完c后(遍历树的宽度)。
可以构建一个递归函数,返回值为void,参数有两个人,一个是输入的字符串,一个是当前遍历层级。
递归函数的终止条件是digits取完所有的数字(代表递归的次数,也是树的深度)。
参考代码
char **res;
int cnt;
typedef struct {
int index;
char str[5];
}Data;
Data data = {0};
char *convert[] = {
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz", // 9
};
void backtracking(char *src, int level)
{
if (strlen(src) == level) {
res[cnt] = (char*)malloc(sizeof(char) * (data.index + 1));
strcpy(res[cnt++], data.str);
return;
}
char *s = convert[src[level] - '0'];
int len = strlen(s);
for (int i = 0; i < len; i++) {
data.str[data.index++] = s[i];
backtracking(src, level + 1);
data.index--; // 回溯
data.str[data.index] = '\0';
}
}
char** letterCombinations(char* digits, int* returnSize) {
if (strlen(digits) == 0) {
*returnSize = 0;
return NULL;
}
res = (char**)malloc(1000 * sizeof(char*));
cnt = 0;
backtracking(digits, 0);
*returnSize = cnt;
return res;
}