一、回溯基础
1 回溯搜索法,通常用于解决穷举问题,列举所以可能性结果,找到想要的结果。类似于枚举多少组合,多少个可能性结果的情况,都需要用回溯法。
2 回溯一般和递归成对出现,处理当前结果,进入递归,再回溯,一一对应。
3 回溯的一般模板(参考代码随想录)
cpp
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
4 应用场景:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
二、77 组合
这道组合问题是经典的回溯题目,按照回溯模板,进行分解:
1 什么条件退出呢?当然是k个元素已经全部找了,即path index达到k,代表写入到path的结果已经有k个了,可以返回值了,c语言里面没有模板函数,因此用数组来代替。当满足退出条件时候,把结果memcpy到result数组;
2 for循环过程,这里需要结合代码随想录上面的讲解结合分析,n可以想象成树的宽度,k可以想象成数的深度,在当前层上,需要考虑从哪一个点开始,结束到哪里(这里后面减枝优化),每一层里面需要做什么操作。
每一层的处理:从start开始,把值写入到path,然后递归到下一次(此时 start就是下一个值),因为每次递归返回到result,还需要把值退回(这里就是回溯),因为其他组合还会用到。
cpp
int* path;
int** result;
int path_idx;
int re_idx;
int* colum ;
void backtracking(int n, int k, int start)
{
if(path_idx == k)
{
result[re_idx] = (int*)calloc(path_idx, sizeof(int));
memcpy(result[re_idx], path, path_idx * sizeof(int));//注意这里多少个字节
colum[re_idx] = path_idx;//记录每一个数组多少entry
re_idx++;
return;
}
for(int i = start; i <= n; i++)
{
path[path_idx++] = i;
/**回溯**/
backtracking(n, k, i + 1);
path_idx--;//退回
}
}
//根据阶乘定义 20中选10个组合最多,达到了18W+:184756种
int** combine(int n, int k, int* returnSize, int** returnColumnSizes) {
if(returnSize == 0)
{
return NULL;
}
result = (int**)malloc(sizeof(int*) * 190000);
path = (int*)malloc(sizeof(int) * 100);//最多20个
path_idx = 0;
re_idx = 0;
colum = (int*)malloc(sizeof(int) * 190000);//最大有18w多个entry
backtracking(n, k, 1);
*returnSize = re_idx;
*returnColumnSizes = colum;
return result;
}
回溯优化可以参考代码随想录上面的讲解,主要理解其思路:每一层遍历时候,如果从n个选k个元素,n=4, k=4,这样的情况,那么当遍历到第二个时候,就没有意义了,因为4个元素里面 从第一个元素开始遍历,是有效的,但是从第二个开始,就少于4个了,后面再遍历无意义了。
修改点:
cpp
for(int i = start; i <= n -(k - path_idx) + 1; i++)
{
path[path_idx++] = i;
/**回溯**/
backtracking(n, k, i + 1);
path_idx--;//退回
}
//k - (path_idx + 1)<= n - i (k代表多少个 path_idx代表的是index,length需要+1)
//i <= n - k + (path_idx + 1)
二、组合III
思路同上,主要记录下两个组合之间不同之处:
1 组合III主要是找到和为n的k个数,因此在函数回退时候,需要判断两个点,其一就是达到k个元素,无论有没有找到目标值n,需要回退;另外,就是当sum等于了当前目标值,那么把path里面的值copy到result数组上。
2 回溯时候,sum的值和path都需要处理。
cpp
int* path;
int** result;
int path_idx;
int re_idx;
int* colum ;
int num ;
void backtracking(int n, int k, int sum, int start)
{
if(path_idx == k)
{
if(sum == n)
{
result[re_idx] = (int*)calloc(path_idx, sizeof(int));
memcpy(result[re_idx], path, path_idx * sizeof(int));
colum[re_idx] = path_idx;//记录每一个数组多少entry
re_idx++;
}
return;
}
for(int i = start; i <= 9; i++)
{
sum += i;
path[path_idx++] = i;
/**回溯**/
backtracking(n, k, sum, i + 1);
sum -= i;
path_idx--;//退回
}
}
int** combinationSum3(int k, int n, int* returnSize, int** returnColumnSizes) {
if(returnSize == 0)
{
return NULL;
}
result = (int**)malloc(sizeof(int*) * 100);
path = (int*)malloc(sizeof(int) * 10);//最多9个
path_idx = 0;
re_idx = 0;
colum = (int*)malloc(sizeof(int) * 100);//
num = 0;
backtracking(n, k, 0, 1);
*returnSize = re_idx;
*returnColumnSizes = colum;
return result;
}
三、17. 电话号码的字母组合
可以根据上面组合的思路,这里不同的是,电话里面的组合,需要用一个字符串数组表示出来,根据题目上面的数字组合,找到对应的字符串。
1 回退条件:给定的数字字符遍历完,把结果path copy到result上面,回退;
2 回溯过程:首先需要找到数字在键盘上的字符串,然后作为一层,进行处理;
特别注意:因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而77. 组合 (opens new window)** 和**216.组合总和III (opens new window)都是求同一个集合中的组合!
cpp
char** result;
char* path;
int path_idx;
int reSize;
char strings[10][10] =
{
"",
"",
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz",
};
void backtracking(char* digits, int index)
{
if(index == strlen(digits))
{
//遍历完成了digits里面的数字
result[reSize] = (int*)calloc(index + 1, sizeof(char));
//这里需要多加一个字符 保证'\0'存进去
memcpy(result[reSize], path, index);
reSize++;
return;
}
int strings_idx = digits[index] - '0';//转成数字
for(int i = 0; i < strlen(strings[strings_idx]); i++)
{
path[path_idx++] = strings[strings_idx][i];
backtracking(digits, index + 1);
path_idx--;
}
}
char** letterCombinations(char* digits, int* returnSize) {
path = (char*)calloc(100, sizeof(char));
result = (char**)calloc(1000, sizeof(char*));
reSize = 0;
path_idx = 0;
*returnSize = 0;
if(strlen(digits) == 0)
{
//*returnSize = 0;
return result;
}
backtracking(digits, 0);
*returnSize = reSize;
return result;
}