代码随想录训练营19day-回溯1

一、回溯基础

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;
}
相关推荐
尼尔森系4 小时前
排序与算法:希尔排序
c语言·算法·排序算法
AC使者4 小时前
A. C05.L08.贪心算法入门
算法·贪心算法
冠位观测者4 小时前
【Leetcode 每日一题】624. 数组列表中的最大距离
数据结构·算法·leetcode
sushang~4 小时前
leetcode203.移除链表元素
数据结构·链表
yadanuof4 小时前
leetcode hot100 滑动窗口&子串
算法·leetcode
可爱de艺艺5 小时前
Go入门之函数
算法
武乐乐~5 小时前
欢乐力扣:旋转图像
算法·leetcode·职场和发展
a_j585 小时前
算法与数据结构(子集)
数据结构·算法·leetcode
刃神太酷啦5 小时前
树(数据结构·)
数据结构·c++·蓝桥杯c++组
清水加冰6 小时前
【算法精练】背包问题(01背包问题)
c++·算法