xym-ee
  • 计算机与嵌入式开发学习
  • 1-1.编程基础
    • C 语言
      • C 中的数据
      • C 语言基础
      • 字符输入输出
      • 函数
      • 数组和指针
      • 字符串处理
      • 存储类别
      • 文件 I/O
      • 复杂数据类型
      • 位操作
      • 预处理和 C 库
    • 数据结构和算法入门
    • leetcode 刷算法题
      • 递归与栈
      • 二叉树与递归
      • 回溯问题
      • 动态规划 1
    • 基本工具和使用
      • shell
      • shell 脚本
      • vim 编辑器
      • 命令行数据整理
      • 命令行环境和配置
  • 1-2.计算机系统基础(CSAPP)
    • 1.计算机基础
    • 2.数据的表示
    • 3.加减运算
    • 4.乘除运算
    • 5.程序的表示转换和链接
    • 6.IA32指令
    • 7.过程调用
    • 10.程序的链接
  • 1-3.数字电路、计算机组成
    • 1.数字电路、virtual circuit board
    • 2.计算机组成/steam:Turing Complete
    • 3.微机原理与接口技术(8086)
  • 1-4.计算机网络
    • 1.从浏览器开始
    • 2.协议栈和网卡
    • 3.网络设备
    • 4.运营商、接入网
    • 5.服务器
    • 6.数据返回浏览器
    • socket编程
  • 1-5.操作系统
    • 0.绪论
      • 1.应用视角的操作系统
      • 2.硬件视角的操作系统
      • 3.数学视角的操作系统
      • 4.状态机模型的应用
    • 1.并发
      • 1.并发 bug 的解决思路
      • 2.互斥
      • 3.同步
      • 4.信号量
      • 5.真实并发
      • 6.调试技巧
      • 7.os kernel 实现
    • 2.虚拟化
      • 1.操作系统上的进程
      • 2.进程的地址空间
      • 3.系统调用和unix shell
      • 4.C 标准库的实现
      • 5.linux 操作系统
      • 6.可执行文件和加载
      • 7.动态链接和加载
      • 8.内核的实现
      • 9.fork 的应用
    • 3.持久化
      • 1.存储设备的原理
      • 2.输入输出设备模型
      • 3.设备驱动程序
      • 4.文件系统 API
      • 5.fat 和 unix 文件系统
      • 6.持久数据的可靠性
    • 总结
  • 2-1.嵌入式裸机开发
    • 嵌入式系统通信接口与协议
    • cortex-m 内核芯片裸机开发
    • MPU
  • 2-2.中等规模系统开发
    • LVGL 图形库
    • 裸机开发的软件框架
    • 基于 rtos 开发
  • 2-3.armv7-m架构与 rtos 原理
    • armv7-m 架构
    • rt-thread 内核实现
    • rt-thread 驱动开发
  • 3-1.linux 应用开发基础
  • 3-2.linux 镜像构建
    • uboot 使用
    • uboot 适配
    • uboot 启动分析
    • uboot 自定义命令
    • linux 内核适配
    • linux 内核启动分析
    • busybox 根文件系统构建
  • 3-3.linux 驱动开发
    • 驱动开发基础
    • sysfs
    • led 驱动
    • 设备树
    • pinctrl 和 gpio 子系统
    • 并发控制
由 GitBook 提供支持
在本页
  • 子集型回溯
  • 模板一
  • 模板二
  • 应用:分割回文串
  • 组合型回溯
  • 排列型回溯
  • N 皇后

这有帮助吗?

  1. 1-1.编程基础
  2. leetcode 刷算法题

回溯问题

  • 组合问题:N个数里面按一定规则找出k个数的集合

  • 切割问题:一个字符串按一定规则有几种切割方式

  • 子集问题:一个N个数的集合里有多少符合条件的子集

  • 排列问题:N个数按一定规则全排列,有几种排列方式

  • 棋盘问题:N皇后,解数独等等

关于回溯。

一个问题两个字符串各选一个组成一个 2 个字符的字符串。最容易想到的是两个循环嵌套。

但是如果说要生成一个长为3、长为 4,或者长度是个参数无法提前知道,那么没法用循环嵌套来表达了。

  • 原问题:构造长为 n 的字符串。

  • 枚举一个字母

  • 子问题:构造长为 n - 1 的字符串。

这个子问题和原问题是很相似的。

这种可以分解的问题,可以使用递归来实现。回溯有一个增量构造答案的过程。

把边界条件和非边界条件的逻辑写对了,那递归代码一定能得到正确的结果。

回溯三问:

  • 当前操作是什么?

  • 子问题是什么?

  • 下一个子问题?

对于电话号码组合,用 path[i] 来记录路径上的字母

  • 枚举 path[i] 处要填入的字母

  • 构造字符串 >= i 的部分

  • 构造字符串 >= i + 1 的部分

dfs(i) -> dfs(i+1)

子集型回溯

模板一

子集型回溯,看每个元素选或者不选。

比如计算 $ [1, 2] $ 所有的子集,每个元素都可以选或者不选,总共就有 4 种情况。

回溯三问,站在输入的角度思考

  • 当前操作?每个第 $ i $ 个数选或不选

  • 子问题?从下标 $ \ge i $ 的数字中构造子集

  • 下一个子问题?从下标 $ \ge i + 1 $ 的数字中构造子集

#define MAX_RESULT  100000

int n;              /* 数组元素个数,终止条件判断 */

int** result;       /* 保存结果的数组 */
int result_len;     /* 结果个数 */
int *column_size;   /* 每个结果的长度 */

int path[10] = {0}; /* 单个结果 */
int path_len;       /* 单个结果的长度 */

void save_result(void)
{
    result[result_len] = malloc(path_len * sizeof(int));
    column_size[result_len] = path_len;

    for (int i=0; i<path_len; i++)
        result[result_len][i] = path[i];

    result_len++;
}

void dfs(int *nums, int i)
{
    if ( i == n )
    {
        save_result();
        return;
    }

    dfs(nums, i+1);   /* 当前操作不选,直接进下一层递归 */

    path[path_len] = nums[i];   /* 当前操作选, */
    path_len++;
    dfs(nums, i+1);

    path_len--;
}

int** subsets(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) 
{
    n           = numsSize;
    result      = malloc(MAX_RESULT * sizeof(int *));
    result_len  = 0;
    column_size = malloc(MAX_RESULT * sizeof(int));
    path_len    = 0;

    dfs(nums, 0);

    *returnSize = result_len;
    *returnColumnSizes = column_size;
    return result;
}

时间复杂度,每次递归,只有选或不选两种情况,$ O(2^n) $ 复制的时间是 O(n) 所以,总的复杂度是 $ O(n*2^n) $

模板二

另一个思路,站在答案的角度去考虑,枚举第一个选谁,第二个选谁,每个节点都是答案

  • 当前操作?枚举一个下标 $ \ge i $ 的数字,加入 path

  • 子问题?从下标 $ \ge i $ 的数字中构造子集

  • 下一个子问题?从下标 $ \ge i + 1 $ 的数字中构造子集

#define MAX_RESULT  100000

int n;              /* 数组元素个数,终止条件判断 */

int** result;       /* 保存结果的数组 */
int result_len;     /* 结果个数 */
int *column_size;   /* 每个结果的长度 */

int path[10] = {0}; /* 单个结果 */
int path_len;       /* 单个结果的长度 */

void save_result(void)
{
    result[result_len] = malloc(path_len * sizeof(int));
    column_size[result_len] = path_len;

    for (int i=0; i<path_len; i++)
        result[result_len][i] = path[i];

    result_len++;
}

void dfs(int *nums, int i)
{
    save_result();

    if ( i == n )   return;
        
    for (int j=i; j<n; j++)
    {
        path[path_len] = nums[j];
        path_len++;
        dfs(nums, j+1);
        path_len--;
    }
}

int** subsets(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) 
{
    n           = numsSize;
    result      = malloc(MAX_RESULT * sizeof(int *));
    result_len  = 0;
    column_size = malloc(MAX_RESULT * sizeof(int));
    path_len    = 0;

    dfs(nums, 0);

    *returnSize = result_len;
    *returnColumnSizes = column_size;
    return result;
}

应用:分割回文串

组合型回溯

组合问题,在子集问题的基础上,增加一个判断逻辑,比如只要选两个数。

相比子集问题,组合问题是可以做一些额外的优化的。

设 path 长度为 m,总共要选 k 个数,那么还需要选 d = k - m 个数。剩下的可以选的数的个数小于 d,那么就不需要递归了,肯定选不出来。这是一个剪枝技巧。

int * single_out = NULL;
int single_out_len = 0;

int **result = NULL;
int result_len = 0;

void backtracking(int n, int k, int start)
{
    //保存结果
    if (single_out_len == k)
    {
        int *temp = malloc(k*sizeof(int));

        for (int i=0; i<k; i++)
        {
            temp[i] = single_out[i];
        }

        result[result_len] = temp;
        result_len++;

        return; //忘记加了
    }

    for (int i=start; i<=n; i++)
    {
        single_out[single_out_len] = i;
        single_out_len++;

        backtracking(n, k, i+1);

        single_out--;
    }
}

int** combine(int n, int k, int* returnSize, int** returnColumnSizes) {

    single_out = (int*)malloc(k*sizeof(int));
    result = malloc(5000*sizeof(int));

    result_len = 0; // 注释掉会出错,为什么?

    backtracking(n, k, 1);

    *returnSize = result_len;

    int **column = malloc(result_len*sizeof(int));

    for(int i=0; i<result_len; i++)
    {
        (*column)[i] = k;
    }

    return result;
}

排列型回溯

全排列,全排列的长度就是数组长度,全排列的个数就是元素个数的阶乘。

N 皇后

char ** single_out = NULL;
int single_Q_num = 0;

char ***result = NULL;
int result_len = 0;


void save_single_result(int n) 
{
    char **temp = (char**)malloc(sizeof(char) * (n + 1)*n);

    for(int i = 0; i < (n + 1)*n; i++) {
        **(temp + i) = ** (single_out + i);
    }

    result[result_len] = temp;
    result_len++;
}


int is_ok(int x, int y, int n) {

    char ** path =  single_out;

    int i, j;
    //检查同一行以及同一列是否有效
    for(i = 0; i < n; ++i) {
        if(path[y][i] == 'Q' || path[i][x] == 'Q')
            return 0;
    }
    //下面两个for循环检查斜角45度是否有效
    i = y - 1;
    j = x - 1;
    while(i >= 0 && j >= 0) {
        if(path[i][j] == 'Q')
            return 0;
        --i, --j;
    }

    i = y + 1;
    j = x + 1;
    while(i < n && j < n) {
        if(path[i][j] == 'Q')
            return 0;
        ++i, ++j;
    }

    //下面两个for循环检查135度是否有效
    i = y - 1;
    j = x + 1;
    while(i >= 0 && j < n) {
        if(path[i][j] == 'Q')
            return 0;
        --i, ++j;
    }

    i = y + 1;
    j = x -1;
    while(j >= 0 && i < n) {
        if(path[i][j] == 'Q')
            return 0;
        ++i, --j;
    }
    return 1;
}

void backtracking(int n, int j)
{
    if (single_Q_num == n)
    {
        save_single_result(n);

        return;
    }

    for (int i=0; i<n; i++)
    {
        if (is_ok(i, j, n) == 1)
        {
            single_out[j][i] = 'Q';
            single_Q_num++;

            backtracking(n, j + 1);

            single_out[j][i] = '.';
            single_Q_num--;

        }
    }
}


char*** solveNQueens(int n, int* returnSize, int** returnColumnSizes) {

    single_Q_num = 0;
    result_len = 0;

    single_out = (char **)malloc(sizeof(char)*(n+1)*n);

    result = (char ***)malloc(sizeof(char**)*100);

    memset(single_out, '.', (n+1)*n);

    for (int i=0;i<n;i++)
        single_out[i][n] = '\0';

    backtracking(n, 0);

    *returnSize = result_len;

    *returnColumnSizes = (int*)malloc(sizeof(int) * result_len);
    for(int i; i < result_len; i++) {
        (*returnColumnSizes)[i] = n;
    }

    return result;

}
上一页二叉树与递归下一页动态规划 1

最后更新于8个月前

这有帮助吗?