《代码随想录第二十八天》——回溯算法理论基础、组合问题、组合总和III、电话号码的字母组合
《代码随想录第二十八天》——回溯算法理论基础、组合问题、组合总和III、电话号码的字母组合
本篇文章的所有内容仅基于C++撰写。
1. 基础知识
1.1 概念
回溯是递归的副产品,它也是遍历树的一种方式,其本质是穷举。它并不高效,但是比暴力循环要快得多,在一些没有更高效解法的题目中,回溯是很好的方法。例如以下题目:
- 组合问题:N个数里面按一定规则找出k个数的集合(组合不强调元素顺序,排列强调元素顺序。)
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
所有的回溯法都可以抽象为树形结构,因为回溯法解决的都是在集合中递归查找子集,所以集合的大小就构成了树的宽度,递归的深度就构成了树的深度。递归有终止条件,所以必然是一棵高度有限的树(N叉树)。
1.2 模板
- 回溯函数的返回值与参数
回溯算法中函数返回值一般为void,但参数需要根据具体题目确定。 - 回溯函数的终止条件
一般搜索到叶子节点就找到了答案,可以结束本次递归。 - 回溯函数的遍历过程
回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度。
- 代码
void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {处理节点;backtracking(路径,选择列表); // 递归回溯,撤销处理结果}
}
2. 组合
2.1 题目
组合
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
示例 2:
输入:n = 1, k = 1
输出:[[1]]
提示:
1 <= n <= 20
1 <= k <= n
2.2 分析
这道题中的n和k值如果很大,使用for循环嵌套将会造成维数灾难。此时回溯法就发挥了很好的作用。我们使用for循环在当前集合内选一个数作为节点,再使用递归法进入下一层(以当前节点值之后的所有元素形成新的集合),然后再使用for循环选数,重复此操作,直到一条树枝上的节点数量达到目标值k。
其中,回溯操作就是在一个树枝上push_back后再进行pop_back的操作,以确保for能在同一个树层上遍历。
注意有个startindex,这个值用来确定for循环从哪个值开始遍历并进入递归
2.3 代码
class Solution {
private:vector<vector<int>> result; // 存放符合条件结果的集合vector<int> path; // 用来存放符合条件结果void backtracking(int n, int k, int startIndex) {if (path.size() == k) {result.push_back(path);return;}for (int i = startIndex; i <= n; i++) {path.push_back(i); // 处理节点backtracking(n, k, i + 1); // 递归path.pop_back(); // 回溯,撤销处理的节点}}
public:vector<vector<int>> combine(int n, int k) {result.clear(); // 可以不写path.clear(); // 可以不写backtracking(n, k, 1);return result;}
};
- 时间复杂度: O(n * 2^n)
- 空间复杂度: O(n)
3. 组合III
3.1 题目
组合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
没有其他符合的组合了。
示例 3:
输入: k = 4, n = 1
输出: []
解释: 不存在有效的组合。
在[1,9]范围内使用4个不同的数字,我们可以得到的最小和是1+2+3+4 = 10,因为10 > 1,没有有效的组合。
提示:
2 <= k <= 9
1 <= n <= 60
3.2 分析
节点累加找到相加值为n的树枝时,说明找到了一条正确的数枝。因此,这道题相对于上一题只是变更了终止条件中的结果记录方式,如果元素个数达到目标值k时,需要判断当前总值是否与目标值相等:相等则将树枝加入结果集再返回,不相等则直接return。同样会使用startindex。
另外这道题会涉及到剪枝操作:如果当前值已经大于了目标值,那么就不需要再继续遍历了。此外,for循环的范围也可以剪枝:i <= 9 - (k - path.size()) + 1,其中:
- 已经选择的元素个数:path.size();
- 所需需要的元素个数为: k - path.size();
- 列表中剩余元素(n-i) >= 所需需要的元素个数(k - path.size())
- 在集合n中至多要从该起始位置 : i <= n - (k - path.size()) + 1,开始遍历
这个剪枝方式是横向的,因为在该类题的场景下,for循环在同层越往后遍历,集合越小,以至于节点数量可能小于了目标值k,这时候就不需要再遍历了,这些树枝就可以被剪掉。
3.3 代码
class Solution {
private:vector<vector<int>> result; // 存放结果集vector<int> path; // 符合条件的结果void backtracking(int targetSum, int k, int sum, int startIndex) {if (sum > targetSum) { // 剪枝操作return; }if (path.size() == k) {if (sum == targetSum) result.push_back(path);return; // 如果path.size() == k 但sum != targetSum 直接返回}for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) { // 剪枝sum += i; // 处理path.push_back(i); // 处理backtracking(targetSum, k, sum, i + 1); // 注意i+1调整startIndexsum -= i; // 回溯path.pop_back(); // 回溯}}public:vector<vector<int>> combinationSum3(int k, int n) {result.clear(); // 可以不加path.clear(); // 可以不加backtracking(n, k, 0, 1);return result;}
};
- 时间复杂度: O(n * 2^n)
- 空间复杂度: O(n)
4. 电话号码的字母组合
4.1 题目
电话号码的字母组合
给定一个仅包含数字 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’] 的一个数字。
4.2 分析
这里的集合不再是数字,因此需要考虑映射问题,可以使用map或定义一个二维数组来完成映射。其原理与上两题类似,只是其中的startindex变为了index,其内涵也不相同。index要在给出的数字用例中遍历,例如“233”或“4567”。因此终止条件就是遍历完这些数字用例。另外注意考虑边界条件。
4.3 代码
// 版本一
class Solution {
private:const string letterMap[10] = {"", // 0"", // 1"abc", // 2"def", // 3"ghi", // 4"jkl", // 5"mno", // 6"pqrs", // 7"tuv", // 8"wxyz", // 9};
public:vector<string> result;string s;void backtracking(const string& digits, int index) {if (index == digits.size()) {result.push_back(s);return;}int digit = digits[index] - '0'; // 将index指向的数字转为intstring letters = letterMap[digit]; // 取数字对应的字符集for (int i = 0; i < letters.size(); i++) {s.push_back(letters[i]); // 处理backtracking(digits, index + 1); // 递归,注意index+1,一下层要处理下一个数字了s.pop_back(); // 回溯}}vector<string> letterCombinations(string digits) {s.clear();result.clear();if (digits.size() == 0) {return result;}backtracking(digits, 0);return result;}
};
- 时间复杂度: O(3^m * 4^n),其中 m 是对应三个字母的数字个数,n 是对应四个字母的数字个数
- 空间复杂度: O(3^m * 4^n)
相关文章:

《代码随想录第二十八天》——回溯算法理论基础、组合问题、组合总和III、电话号码的字母组合
《代码随想录第二十八天》——回溯算法理论基础、组合问题、组合总和III、电话号码的字母组合 本篇文章的所有内容仅基于C撰写。 1. 基础知识 1.1 概念 回溯是递归的副产品,它也是遍历树的一种方式,其本质是穷举。它并不高效,但是比暴力循…...

PromptSource官方文档翻译
目录 核心概念解析 提示模板(Prompt Template) P3数据集 安装指南 基础安装(仅使用提示) 开发环境安装(需创建提示) API使用详解 基本用法 子数据集处理 批量操作 提示创建流程 Web界面操作 手…...

USB子系统学习(四)用户态下使用libusb读取鼠标数据
文章目录 1、声明2、HID协议2.1、描述符2.2、鼠标数据格式 3、应用程序4、编译应用程序5、测试6、其它 1、声明 本文是在学习韦东山《驱动大全》USB子系统时,为梳理知识点和自己回看而记录,全部内容高度复制粘贴。 韦老师的《驱动大全》:商…...

Ansible简单介绍及用法
一、简介 Ansible是一个简单的自动化运维管理工具,基于Python语言实现,由Paramiko和PyYAML两个关键模块构建,可用于自动化部署应用、配置、编排task(持续交付、无宕机更新等)。主版本大概每2个月发布一次。 Ansible与Saltstack最大的区别是…...
目前推荐的优秀编程学习网站与资源平台,涵盖不同学习方式和受众需求
一、综合教程与互动学习平台 菜鸟教程 特点:适合零基础新手,提供免费编程语言教程(Python、Java、C/C++、前端等),页面简洁且包含大量代码示例,支持快速上手。适用人群:编程入门者、需要快速查阅语法基础的学习者。W3Schools 特点:专注于Web开发技术(HTML、CSS、JavaS…...
软件工程-软件需求规格说明(SRS)
基本介绍 目标 便于用户、分析人员、设计人员进行交流 支持目标软件系统的确认(验收) 控制系统进化过程(追加需求):拥有版本记录表 需要在软件分析完成后,编写完成软件需求说明书。 具体标准可参考GB…...
运维_Mac环境单体服务Docker部署实战手册
Docker部署 本小节,讲解如何将前端 后端项目,使用 Docker 容器,部署到 dev 开发环境下的一台 Mac 电脑上。 1 环境准备 需要安装如下环境: Docker:容器MySQL:数据库Redis:缓存Nginx&#x…...

UE5.5 PCGFrameWork--GPU CustomHLSL
在上一篇UE5.5 PCGFrameWork使用入门-CSDN博客 大致介绍了UE5 PCG框架的基本使用. 本篇探索PCGFrame的高级应用--GPU点云。也就是利用GPU HLSL编程对点云进行操纵,可以大幅度提升点云生成效率。 目前在UE5 PCG框架中,点云GPU的应用大致分为三类: Point…...
RabbitMQ 如何设置限流?
RabbitMQ 的限流(流量控制)主要依赖于 QoS(Quality of Service) 机制,即 prefetch count 参数。这个参数控制每个消费者一次最多能获取多少条未确认的消息,从而避免某个消费者被大量消息压垮。 1. RabbitMQ…...

json格式,curl命令,及轻量化处理工具
一. JSON格式 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它基于一个子集的JavaScript编程语言,使用人类易于阅读的文本格式来存储和表示数据。尽管名字中有“JavaScript”,但JSON是语言无关的,几…...

Postman面试问题
在 API 测试领域,Postman 已成为最流行的工具之一。无论是功能测试、自动化测试,还是接口调试,Postman 都扮演着重要角色。而在软件测试面试中,Postman 相关问题更是高频考点。如果你正在准备面试,赶紧看看这些Postman…...

【JVM详解四】执行引擎
一、概述 Java程序运行时,JVM会加载.class字节码文件,但是字节码并不能直接运行在操作系统之上,而JVM中的执行引擎就是负责将字节码转化为对应平台的机器码让CPU运行的组件。 执行引擎是JVM核心的组成部分之一。可以把JVM架构分成三部分&am…...
esp32 udp 客户端 广播
esp32 udp 客户端 广播 #include "bsp_udpc.h"// #include "com_config.h" // #include "com_xqueue.h"#include "bsp_udpc.h" #define TAG "bsp_udpc"#include <string.h> #include <sys/param.h> #include &q…...
nginx日志存储access日志和error保留180天,每晚把前一天的日志文件压缩成tar.gz
logrotate日志分割时,rotate参数是必须要加的 在logrotate的配置文件中,rotate参数用于指定保留的旧日志文件数量。如果不配置rotate参数,默认是0个,也就是只允许存在一份日志,刚切分出来的日志会马上被删除 l…...

【Java】多线程和高并发编程(四):阻塞队列(上)基础概念、ArrayBlockingQueue
文章目录 四、阻塞队列1、基础概念1.1 生产者消费者概念1.2 JUC阻塞队列的存取方法 2、ArrayBlockingQueue2.1 ArrayBlockingQueue的基本使用2.2 生产者方法实现原理2.2.1 ArrayBlockingQueue的常见属性2.2.2 add方法实现2.2.3 offer方法实现2.2.4 offer(time,unit)方法2.2.5 p…...

C#控件开发6—旋转按钮
按钮功能:手自动旋转,标签文本显示、点击二次弹框确认(源码在最后边); 【制作方法】 找到控件的中心坐标,画背景外环、内圆;再绘制矩形开关,进行角度旋转即可获得; 【关…...

在亚马逊云科技上云原生部署DeepSeek-R1模型(下)
在本系列的上篇中,我们介绍了如何通过Amazon Bedrock部署并测试使用了DeepSeek模型。在接下来的下篇中小李哥将继续介绍,如何利用亚马逊的AI模型训练平台SageMaker AI中的,Amazon Sagemaker JumpStart通过脚本轻松一键式部署DeepSeek预训练模…...
C# COM 组件在.NET 平台上的编程介绍
.NET学习资料 .NET学习资料 .NET学习资料 一、COM 组件简介 COM(Component Object Model)即组件对象模型,是一种微软提出的软件组件技术,它允许不同的软件模块在二进制层面进行交互。COM 组件可以用多种编程语言开发࿰…...

火热的大模型: AIGC架构解析
文章目录 一、背景介绍二、架构描述数据层模型层(MaaS)服务层(PaaS)基础设施层(IaaS)应用层 三、架构分析四、应用场景与价值4.1 典型场景4.2 价值体现 五、总结 一、背景介绍 火热的大模型,每…...

Android LifecycleOwner 闪退,java 继承、多态特性!
1. 闪退 同意隐私政策后,启动进入游戏 Activity 闪退 getLifecycle NullPointerException 空指针异常 FATAL EXCEPTION: main Process: com.primer.aa.gg, PID: 15722 java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.primer.aa.…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...

页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...

苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...