回溯算法—组合问题

news2025/5/21 21:24:00

文章目录

  • 介绍
  • 应用问题
  • 基本流程
  • 算法模版
  • 例题
    • (1)组合
    • (2)电话号码的字母组合

介绍

回溯算法实际上是 一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。

应用问题

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 棋盘问题:N皇后,解数独等等

基本流程

  • 选择:在当前步骤中,从可选的选择中选择一个。
  • 验证:检查选择是否满足问题的约束条件和限制。
  • 递归:进入下一步骤,继续选择和验证。
  • 撤销:如果选择不满足问题要求,回溯到上一步,撤销当前选择,并尝试其他选择。

回溯法解决的问题都可以抽象为树形结构

集合的大小就构成了树的宽度,递归的深度就构成了树的深度

算法模版

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

递归嵌套for循环

回溯三部曲:

  1. 递归函数的参数和返回值:返回值一般为void
  2. 确定终止条件
  3. 单层搜索的逻辑

例题

(1)组合

77. 组合 - 力扣(LeetCode)

给定两个整数 nk,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

示例 1:

输入:n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

组合是无序的,[1,2]和[2,1]一致

每一个节点都是一层for循环,从startIndex开始

tree
  1. 递归函数的参数和返回值:

    void backTracking(int n, int k, int startIndex)
    
  2. 确定终止条件

    到达叶子节点,path数组大小到达k

    if (pathtop == k) 
    
  3. 单层搜索的逻辑

    for循环从startIndexn中选择数字(startIndex控制每次搜索时的起始位置,本题第一次调用时传1

    先处理当前i节点;
    再递归:传入i+1控制下一层递归起始位置;
    回溯:退出递归调用之后,需要回溯到之前的状态,来尝试其他数字并构建其他组合。因此 pathtop 减 1,i + 1退出递归后在当前循环还是i

    for (int i = startIndex; i <= n; i++) {
            path[pathtop++] = i; // 存入结果
            backTracking(n, k, i + 1); // 递归,传入i+1,下一层递归起始位置
            pathtop--;
    }
    

    for循环横向遍历,递归纵向遍历,回溯不断调整结果集

int* path;
int pathtop;
int** ret;
int rettop;
void backTracking(int n, int k, int startIndex) {
    if (pathtop == k) {
        int* temp = (int*)malloc(sizeof(int) * k);
        for (int i = 0; i < k; i++) {
            temp[i] = path[i];
        }
        ret[rettop++] = temp;
        return;
    }
    for (int i = startIndex; i <= n; i++) {
        path[pathtop++] = i;
        backTracking(n, k, i + 1);
        pathtop--;
    }
}
int** combine(int n, int k, int* returnSize, int** returnColumnSizes) {
    pathtop = 0;
    rettop = 0;
    path = (int*)malloc(sizeof(int) * k);
    ret = (int**)malloc(sizeof(int*) * 1000000);
    backTracking(n, k, 1);
    
    *returnSize = rettop;
    (*returnColumnSizes) = (int*)malloc(sizeof(int) * (*returnSize));
    for (int i = 0; i < (*returnSize); i++) {
        (*returnColumnSizes)[i] = k;
    }
    return ret;
}

剪枝

时间复杂度:叶子个数乘叶子到根的路径长度

for循环在寻找起点的时候要有一个范围,如果这个起点到集合终止之间的元素已经不够题目要求的k个元素了,就没有必要搜索了

例如如果n = 4, k = 4情况下

nk4

已经选择的数量:pathTop

还需要选择的数量:k - pathTop

集合n中至多要从n - (k - pathTop) + 1 开始遍历

[1,2,3,4]

n=4,k=3,假设选了一个了,还要选两个,那么至多从n - 2 + 1 = 3

假设选了0个,那么还要选三个,那么至多从n - 3 + 1 = 2

 for (int i = startIndex; i <= (n - k + pathtop + 1); i++) {
        path[pathtop++] = i;
        backTracking(n, k, i + 1);
        pathtop--;
 }

(2)电话号码的字母组合

17. 电话号码的字母组合 - 力扣(LeetCode)

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

输入:digits = ""
输出:[]
  1. 递归函数的参数和返回值:

    void backTracking(char* digits, int length, int index)
    
  2. 确定终止条件

    遍历到字符串末尾

     if (pathTop == length) 
    
  3. 单层搜索的逻辑

    创建一个map[10][5] 来存储不同号码对应的字母

    for循环遍历的是map[i] 的长度
    在这里插入图片描述

char** ret;
int retTop;
char* path;
int pathTop;
char map[10][5] = {"\0",    "\0",    "abc\0",  "def\0", "ghi\0",
                   "jkl\0", "mno\0", "pqrs\0", "tuv\0", "wxyz\0"};
void backTracking(char* digits, int length, int index) {
    // 当遍历到字符串末尾时,将当前组合加入结果集中
    if (index == length) {
        char* temp = (char*)malloc(sizeof(char) * (pathTop + 1));
        memcpy(temp, path, sizeof(char) * pathTop);
        temp[pathTop] = '\0';
        ret[retTop++] = temp; 
        return;
    }
    // 获取当前数字对应的字符集合的长度
    int num = digits[index] - '0';
    int letterLength = strlen(map[num]);
    // 遍历当前数字对应的字符集合
    for (int i = 0; i < letterLength; i++) {
        // 当前组合长度达到字符串长度时,退出循环
        if (pathTop >= length) {
            break;
        }
        // 将当前字符加入当前组合中
        path[pathTop++] = map[num][i];
        backTracking(digits, length, index + 1);
        pathTop--;
    }
}

// 主函数,生成数字键盘对应的所有字母组合
char** letterCombinations(char* digits, int* returnSize) {
    retTop = pathTop = 0;
    int maxPathLength = strlen(digits) * 4; 
    
    path = (char*)malloc(sizeof(char) * (maxPathLength + 1));
    ret = (char**)malloc(sizeof(char*) * 1000);
    
    // 初始化结果集数组为空
    for (int i = 0; i < 1000; ++i) {
        ret[i] = NULL;
    }
    int length = strlen(digits);
    
    // 输入字符串长度为0返回空结果集
    if (length == 0) {
        *returnSize = 0;
        return ret;
    }
    
    backTracking(digits, length, 0);
    
    *returnSize = retTop; 
    return ret; 
}

参考:

  1. 代码随想录 (programmercarl.com)
  2. 回溯算法套路②组合型回溯+剪枝
  3. 17. 电话号码的字母组合 - 力扣(LeetCode)


如有错误烦请指正。

感谢您的阅读

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1661000.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

明火检测实时识别报警:视觉算法助力安全生产管理

背景与现状 在各种工作、生产环境下&#xff0c;明火的存在往往是潜在的安全隐患。无论是加油站、化工园区、仓储场所还是校园&#xff0c;明火一旦失控就会引发火灾&#xff0c;造成严重的人员伤亡和财产损失。传统的明火检查手段主要依赖于人工巡查和定期的消防检查&#xf…

可微分矢量图形光栅化用于编辑和学习

图1. 我们引入了一种通过反向传播将光栅和矢量域联系起来的矢量图形可微分光栅化器。可微分光栅化实现了许多新颖的矢量图形应用。&#xff08;a&#xff09;在几何约束下&#xff0c;通过局部优化图像空间度量&#xff08;如不透明度&#xff09;来实现交互式编辑。&#xff0…

leetcode刷题:对称二叉树

题目&#xff1a; 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true 示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xff1a;false 提示&#xf…

基于Springboot+Vue的Java项目-毕业就业信息管理系统开发实战(附演示视频+源码+LW)

大家好&#xff01;我是程序员一帆&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &am…

Agent AI:智能代理的未来

&#x1f388;写在前面 &#x1f64b;‍♂️大家好呀&#xff0c;我是超梦梦梦梦 &#x1f64b;‍♂️ 小伙伴们如果在学习过程中有不明白的地方&#xff0c;欢迎评论区留言提问&#xff0c;小梦定知无不言&#xff0c;言无不尽。 目录 一、Agent AI的起源与发展 二、Agent A…

使用GitLab自带的CI/CD功能在K8S集群里部署项目(四)

前置内容&#xff1a; 通过Docker Compose部署GitLab和GitLab Runner&#xff08;一&#xff09; 使用GitLab自带的CI/CD功能在本地部署项目&#xff08;二&#xff09; 使用GitLab自带的CI/CD功能在远程服务器部署项目&#xff08;三&#xff09; 一、K8S集群信息 节点名称…

誉天教育近期开班计划

云计算HCIE 晚班 2024/5/13 大数据直通车 周末班 2024/5/25 数通直通车 晚班 2024/5/27 云服务HCIP 周末班 2024/6/1 云计算HCIP 周未班 2024/6/1 RHCA442 晚班 2024/6/17 周末班&#xff1a;周六-周日9:00-17:00晚 班&#xff1a;周一到周五19:00-21:30注&…

【C++ 内存管理】深拷贝和浅拷贝你了解吗?

文章目录 1.深拷贝2.浅拷贝3.深拷贝和浅拷贝 1.深拷贝 &#x1f34e; 深拷⻉: 是对对象的完全独⽴复制&#xff0c;包括对象内部动态分配的资源。在深拷⻉中&#xff0c;不仅复制对象的值&#xff0c;还会复制对象所指向的堆上的数据。 特点&#xff1a; &#x1f427;① 复制对…

IF:23.2|从实验室到田间,微生物干预提高植物抗逆

期刊&#xff1a;Nature Food 影响因子&#xff1a;23.2 发表时间&#xff1a;2023年10月 本研究介绍了一种名为SynCom的微生物组合&#xff0c;该组合Rhodococcus erythropolis和Pseudomonas aeruginosa两种微生物组成。这两种微生物能够帮助水稻抵抗铝毒害和磷缺乏&…

论文阅读:The Unreasonable Ineffectiveness of the Deeper Layers 层剪枝与模型嫁接的“双生花”

作者实证研究了针对流行的开放式预训练 LLM 系列的简单层修剪策略&#xff0c;发现在不同的 QA 基准上&#xff0c;直到去掉一大部分&#xff08;最多一半&#xff09;层&#xff08;Transformer 架构&#xff09;后&#xff0c;性能的下降才会降到最低。为了修剪这些模型&…

Netty底层数据交互源码分析

文章目录 1. 前题回顾2. 主线流程源码分析3. Netty底层的零拷贝4. ByteBuf内存池设计 书接上文 1. 前题回顾 上一篇博客我们分析了Netty服务端启动的底层原理&#xff0c;主要就是将EventLoop里面的线程注册到了Select中&#xff0c;然后调用select方法监听客户端连接&#xf…

HashSet扩容机制

HashSet底层是HashMap,第一次添加的时候,table数组扩容到16,临界值是16*加载因子(默认是0.75),到达临界值进行扩容。 HashSet<Integer> hashSet = new HashSet<>();hashSet.add(5);hashSet.add(2);hashSet.add(5);hashSet.add(8);hashSet.add(1);当new一个H…

使用Matplotlib绘制正弦和余弦函数曲线

前言 在数据可视化领域&#xff0c;Matplotlib是一个功能强大的Python库&#xff0c;它允许用户创建各种静态、交互式和动画图形。本文将引导您通过一个简单的示例&#xff0c;学习如何使用Matplotlib绘制正弦和余弦函数曲线。 第一步&#xff1a;导入必要的库&#xff1a; …

Java医院绩效考核系统源码maven+Visual Studio Code一体化人力资源saas平台系统源码

Java医院绩效考核系统源码mavenVisual Studio Code一体化人力资源saas平台系统源码 医院绩效解决方案包括医院绩效管理&#xff08;BSC&#xff09;、综合奖金核算&#xff08;RBRVS&#xff09;&#xff0c;涵盖从绩效方案的咨询与定制、数据采集、绩效考核及反馈、绩效奖金核…

【数学】泰勒公式

目录 引言 一、泰勒公式 1.泰勒公式及推导 &#xff08;1&#xff09;推导 &#xff08;2&#xff09;公式 2.泰勒中值定理 &#xff08;1&#xff09;定理1&#xff08;佩亚诺余项&#xff09; &#xff08;2&#xff09;定理2&#xff08;拉格朗日余项&#xff09; …

python 根据网址和关键词批量下载影像

最近用到了GLASS的LAI产品&#xff0c;但这个产品的文件夹分得很细&#xff0c;我需要的影像又有8个瓦片&#xff0c;一个一个点击很麻烦&#xff0c;于是探索了批量下载的方法 一、下载1幅 import requests import re import os import requests import re# 网页URLurl &…

一、linux内存泛解

地址空间 逻辑地址经过段机制转换成线性地址&#xff0c;线性地址经过页机制转换成物理地址 linux采用虚拟内存管理&#xff0c;进程都有各自的地址空间这个空间整体是块大小为4G的线性虚拟空间用户看到的都是虚拟地址&#xff0c;而非实际的物理地址&#xff0c;这既能保护操…

ASP.NET学生成绩管理系统

摘要 本系统依据开发要求主要应用于教育系统&#xff0c;完成对日常的教育工作中学生成绩档案的数字化管理。开发本系统可使学院教职员工减轻工作压力&#xff0c;比较系统地对教务、教学上的各项服务和信息进行管理&#xff0c;同时&#xff0c;可以减少劳动力的使用&#xf…

Kitti数据集初识

kitti数据集 00 无人驾驶数据集汇总 无人驾驶数据集汇总(一)附下载地址 0.kitti数据简介 kitti数据集是一个为立体,光流,视觉里程计,3D目标检测和3D跟踪等任务而开发采集的基准数据集。 它利用了Annieway自动驾驶平台,配备了1个OXTS RT 3003,1台Velodyne HDL-64E激光…

2024-AIDD-人工智能药物设计-AlphaFold3

AlphaFold3&#xff5c;万字长文解读 AlphaFold3预测所有分子相互作用准确结构 AlphaFold3 自2021年AlphaFold2问世以来&#xff0c;科研工作者们便开始利用这一蛋白结构预测模型来详细描绘众多蛋白质的结构、探索新药。近日&#xff0c;Google DeepMind公司推出了其最新产品…