代码随想录算法训练营第60期第二十二天打卡
大家好!我们今天来到了一个全新的章节,回溯算法,那究竟什么是回溯算法,我们应该如何理解回溯算法,以及回溯算法可以解决的题目,我们今天就来一探究竟。
第一部分 回溯算法理论基础
其实我可以告诉大家的是回溯算法其实本质上就是暴力,其实并不是什么高效的算法,但是有一些类型的题目不用回溯算法就解决不了,其实我们后面的图论里面会有一道题叫做所有可达的路径,那里其实就是递归与回溯相辅相成,那里的dfs(深度优先搜索)本质上就是递归,其实回溯本质就是暴力枚举(穷举),我们的确可以通过一些剪枝的操作来尽可能减小时间复杂度,但其实我们还是改变不了回溯的本质是暴力,有一些问题能暴力搜素出来就不错了,所以我们不得不采用回溯的算法。
那么回溯算法可以解决什么样的题目呢?首先我们可以解决以下几类问题:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
这些我们在后面都会有所涉及,大家只需要了解一下就可以了,还有回溯的模板,回溯其实是在一棵树上进行操作的,回溯法解决的都是在集合中递归查找子集,递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。
那么提到递归的模板其实大家刷题刷多了逐渐有了自己的理解以后自然就会总结出属于自己的模板,我在这里简单提一下:
void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {处理节点;backtracking(路径,选择列表); // 递归回溯,撤销处理结果}
}
先找到递归的终止条件,加下来是遍历搜索符合条件的节点,然后回溯,因为我们还会有下一次操作,递归与回溯两者不可分割,就比如说我要找3的全排列的所有情况,1后面可以有2,3,如果我找全了一种情况,我就必须回溯才能找全所有情况,这个大家要注意。好了那我们就开始尝试一下今天的回溯算法的题目了。
第一题对应力扣编号为77的题目组合
我们来到今天的第一题,这个应该可以算是回溯算法的入门题目了,我们来看一下题目要求:
题目的意思是给出两个数求出给定范围内的给定个数的组合数,这个如果不用回溯算法的话真的似乎没有其他的方法了,其实我给大家举一个例子,比如说1,2,3,4我要找出所有大小为2的组合,其实大家可以很轻松的写出代码,就是两层嵌套的for循环即可:
#include <iostream>
#include <vector>using namespace std;
vector<int> vec = {1, 2, 3, 4};
int main()
{for (int i = 0; i < vec.size(); ++i){for (int j = i + 1; j < vec.size(); ++j){cout << vec[i] << " " << vec[j] << '\n'; }}return 0;
}
如果是找出所有大小为3的组合呢?大家也可以快速写出代码:
#include <iostream>
#include <vector>using namespace std;
vector<int> vec = {1, 2, 3, 4};
int main()
{for (int i = 0; i < vec.size(); ++i){for (int j = i + 1; j < vec.size(); ++j){for (int k = j + 1; k < vec.size(); ++k){cout << vec[i] << " " << vec[j] << " " << vec[k] << '\n';}}}return 0;
}
其实我们可以看出,一旦组合的长度大了,我们的for嵌套很多就会导致超时,因为我们需要想一种方法,我们要先清楚一个问题其实我们的回溯算法本质是通过一棵树来实现的:
但是大家还要时刻注意我们都要取不同的元素,比如说为什么取了2后面不会出现【2,1】这种情况就是考虑到了组合是无序的,【1,2】与【2,1】算作同一个,而且每一个元素只能去一次,自然也不会出现【2,2】的情况,大家了解了树形结构以后,接下来就是很重要的写代码环节,结合我们二叉树章节的递归三部曲,我们就可以尝试写出本题的代码:
class Solution {
private:vector<int> path;vector<vector<int>> result;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) {path.clear();result.clear();backtracking(n, k, 1);return result;}
};
我们来理解一下,其实如果初次接触回溯算法的话还是很难的,首先我们借助一个数组存储我们当前正在搜索到路径,当长度够了的时候我们就到了单次递归的终止条件,我们就把我们搜索到的路径添加到二维数组里这样我们就返回二维数组,这是我们题目要求我们返回的结果,大家尤其注意我中间的递归逻辑,我们一定不要忘记回溯,如果我们搜索到了【1,2】这条路只有回溯我们才可以搜索到【1,3】这条路,这就是回溯的意义,不要忘记任何的可行解,而且时刻注意我们参数的变化,注意我们起始的索引,保证不重复就可以了。
第二题对应力扣编号为216的题目组合总和III
我们来到了今天的第二道题目,其实前面的那道题我们是可以做剪枝操作的,但是我们先不看我们先理解好回溯算法再去考虑如何优化,我们来看一下这道题目是什么意思:
题目意思很好明白,其实比上一题给我们添加了一个限制条件而已,就是要求我找到的组合的和必须是给定值,我们应该如何考虑呢?其实我们还是得注意递归的终止条件以及回溯过程,如果说我们找到了一个符合要求的组合我们就添加进二维数组里面最后返回二维数组即可:
class Solution {
private:vector<int> path;vector<vector<int>> result;//解释一下我们递归函数的参数与返回值这不也是我们递归三部曲的第一步第一个参数targetSum是题目给的目标和//k表示要求的长度,sum存储的是当前的和,startIndex是起始的索引就是可以搜索的元素void backtracking(int targetSum, int k, int sum, int startIndex){//这里递归的终止条件与上面的题目有所不同其实要求会更多if (path.size() == k){//不仅仅长度要求需要满足而且和也需要满足才可以if (sum == targetSum){result.push_back(path);return;}}//单层递归的逻辑for (int i = startIndex; i <= 9; ++i){//首先将该元素添加到sum种然后添加到数组中sum += i;path.push_back(i);backtracking(targetSum, k, sum, i + 1); // 注意i+1调整startIndex//然后回溯就是减去i然后再将当前元素退出数组sum -= i;path.pop_back();}}
public:vector<vector<int>> combinationSum3(int k, int n) {result.clear();path.clear();backtracking(n, k, 0, 1);return result;}
};
具体的过程我在代码注释里面解释的很清楚,大家多多思考,理解回溯不容易。
第三题对应力扣题号为17的题目电话号码的字母组合
这是今天的最后一道题目,我们直接来看一下题目要求是什么:
其实这里存在一个映射,就是一个数字对应几个字母,给出我们两个数字字符求出所有存在的字母的组合,我们应该如何思考?
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';string letters = letterMap[digit]; // 取数字对应的字符集//这样我们开始找对应的字母for (int i = 0; i < letters.size(); ++i){s.push_back(letters[i]);backtracking(digits, index + 1);s.pop_back();}}vector<string> letterCombinations(string digits) {s.clear();result.clear();//如果给出的数字字符串是0就直接返回空数组就可以if (digits.size() == 0) return result;backtracking(digits, 0);return result;}
};
这个题目有点抽象容易搞混,我们的映射就实现了数字与字母的关系,我们还需要去遍历当前要求的数字所对应的字母字符串在这期间我们才可以使用递归与回溯,一个个字母去找,找完第一个数字对应的再去找下一个直到达到了题目给出数字字符串的长度即可。
今日总结
回溯算法其实是很重要的,我们在后面的图论会反复使用,大家理解好递归三部曲就好,搞明白我们回溯的对象是谁,具体题目具体分析就可以了。今天我们就到这里,我们下一次再见!
相关文章:

代码随想录算法训练营第60期第二十二天打卡
大家好!我们今天来到了一个全新的章节,回溯算法,那究竟什么是回溯算法,我们应该如何理解回溯算法,以及回溯算法可以解决的题目,我们今天就来一探究竟。 第一部分 回溯算法理论基础 其实我可以告诉大家的是…...

自主机器人模拟系统
一、系统概述 本代码实现了一个基于Pygame的2D自主机器人模拟系统,具备以下核心功能: 双模式控制:支持手动控制(WASD键)和自动导航模式(鼠标左键设定目标) 智能路径规划:采用改进型…...

基于QT的仿QQ音乐播放器
一、项目介绍 该项目是基于QT开发的⾳乐播放软件,界面友好,功能丰富,主要功能如下: 窗口hand部分: 点击最小化按钮,窗口最小化 点击最大化按钮,窗口最大化 点击关闭按钮,程序退出 …...

腾讯研究院:《工业大模型应用报告》(文末附下载方式)
腾讯研究院发布的《工业大模型应用报告》是一份系统探讨大模型技术在工业领域落地实践的研究成果。该报告基于腾讯在人工智能、云计算及产业互联网的实践经验,结合国内外典型案例,深入分析了工业大模型的行业价值、关键技术、应用场景及未来趋势。报告指…...
C语言-指针(一)
目录 指针 内存 概念 指针变量 取地址操作符(&) 操作符“ * ” 指针变量的大小 注意 指针类型的意义 作用 void * 指针 const修饰指针变量 const放在*前 const放在*后 双重const修饰 指针的运算 1.指针 - 整数 2.指针 - 指针 3.指…...

【DeepMLF】具有可学习标记的多模态语言模型,用于情感分析中的深度融合
这是一篇我完全看不懂的论文,写的好晦涩,适合唬人,所以在方法部分我以大白话为主 abstract 在多模态情感分析(MSA)中,多模态融合已经得到了广泛的研究,但融合深度和多模态容量分配的作用还没有得到充分的研究。在这项工作中,我们将融合深度、可扩展性和专用多模容量作…...

uniapp如何获取安卓原生的Intent对象
通过第三方app唤起,并且获取第三方app唤起时携带的参数 因为应用a唤起应用b时,应用b第一时间就要拿到参数token,所以需要将获取参数的方法写在APP.vue中的onLaunch钩子里,如果其他地方要用可以选择vuex或者采用本地缓存。 uniapp中plus.run…...
implement the “pixel-wise difference“
根据在处理图像数据的来源和格式的不同,在具体实现“两幅图像残差比较”的时候,分为两类方法。 类型一:PyTorch 的 Tensor 图像格式 imgs_pil_o [transforms.ToPILImage()(img_o) for img_o in imgs_o] imgs_pil_w [transforms.ToPILImag…...

tinycudann安装过程加ubuntu18.04gcc版本的升级(成功版!!!!)
使用的是 Linux,安装以下软件包 sudo apt-get install build-essential git安装 CUDA 并将 CUDA 安装添加到您的 PATH。 例如,如果您有 CUDA 12.6.3,请将以下内容添加到您的/usr/local/~/.bashrcexport PATH"/usr/local/cuda-12.6.3/bi…...

Android 实现一个隐私弹窗
效果图如下: 1. 设置同意、退出、点击用户协议、点击隐私协议的函数参数 2. 《用户协议》、《隐私政策》设置成可点击的,且颜色要区分出来 res/layout/dialog_privacy_policy.xml 文件 <?xml version"1.0" encoding"utf-8"?&…...
Oracle无法正常OPEN(三)
在Oracle数据库中,如果几个数据文件丢失,导致数据库无法启动,报错“ORA-01157: cannot identify/lock data file 2 - see DBWR trace file”,如果没有物理备份的情况下,位于丢失数据文件的数据是无法找回的,…...
本地服务验证-仙盟创梦IDE-智能编程,编程自动备份+编程审计
本地服务验证server using System; using System.Net;class Program {static void Main(){HttpListener listener new HttpListener();listener.Prefixes.Add("http://localhost:8080/");listener.Start();Console.WriteLine("服务器已启动,监听中…...

[学成在线]22-自动部署项目
自动部署 实战流程 下边使用jenkins实现CI/CD的流程。 1、将代码使用Git托管 2、在jenkins创建任务,从Git拉取代码。 3、拉取代码后进行自动构建:测试、打包、部署。 首先将代码打成镜像包上传到docker私服。 自动创建容器、启动容器。 4、当有代…...

Golang|使用函数作为参数和使用接口的联系
函数作为数据类型的一种,可以成为其他函数的参数。在 Go(Golang) 中,函数作为参数 和 接口(interface),本质上都和抽象、灵活调用有关 —— 都是让代码更灵活、更可扩展的手段。不过它们各有侧重…...

MATLAB技巧——norm和vecnorm两个函数讲解与辨析
在 MATLAB 中,norm 和 vecnorm 是两个用于计算向量或矩阵范数的函数,虽然它们的功能相似,但在使用场景和适用性上存在一些区别。本文将详细解释这两个函数的用途、功能以及如何选择合适的函数。 文章目录 norm函数用法范数类型vecnorm函数用法范数类型选择合适的函数示例对比…...

ubuntu的libc 库被我 sudo apt-get --reinstall install libc6搞没了
我系统的libc 没了 今天为了运行一个开源的yuv 播放器,在运行的时候提醒 Inconsistency detected by ld.so: dl-call-libc-early-init.c: 37: _dl_call_libc_early_init: Assertion sym ! NULL failed!然后听从AI 的建议 当我去执行ls 时,系统提示 就这…...

Ubuntu搭建Conda+Python开发环境
目录 一、环境说明 1、测试环境为ubuntu24.04.1 2、更新系统环境 3、安装wget工具 4、下载miniconda安装脚本 二、安装步骤 1、安装miniconda 2、source conda 3、验证版本 4、配置pip源 三、conda用法 1、常用指令 一、环境说明 1、测试环境为ubuntu24.04.1 2、更…...
智能工厂规划学习——深入解读数字化工厂规划与建设方案
项目总体思路聚焦于通过智能制造和数字化工厂建设,来优化企业战略并提升信息化水平。首先,企业需学习先进国家已经验证的先进经验,并紧跟其正在变革的方向,以确保自身发展的前瞻性和竞争力。 在企业战略层面,企业正从以产品为中心的业务模式,逐步转变为以服务中心…...
【学习笔记】深入理解Java虚拟机学习笔记——第2章 Java内存区域与内存溢出异常
第2章 Java内存区域与内存溢出异常 2.1 概述 略 2.2 运行时数据区域 2.2.1 程序计数器 线程私有,记录执行的字节码位置 2.2.2 Java 虚拟机栈 线程私有,存储一个一个的栈帧,通过栈帧的出入栈来控制方法执行。 -栈帧:对应一个…...

Python全流程开发实战:基于IMAP协议安全下载个人Gmail邮箱内所有PDF附件
在日常办公场景中,面对成百上千封携带PDF附件的邮件,手动逐一下载往往耗时耗力,成为效率瓶颈。如何通过代码实现“一键批量下载”?本文将以**“Gmail全量PDF附件下载工具”**开发为例,完整拆解从需求分析到落地交付的P…...
【验证技能】VIP项目大总结
VIP项目快做一段落了,历时一年半,也该要一个大汇总。 VIP简介 VIP开发流程 VIP难点 进程同步 打拍插入不同bit位宽数据问题。 动态升降lane VIP做的不好的地方和改进想法 各层之间交互 testsuite两端关键 所有层的实现架构不统一 VIP经验 ** 架构…...

Pytest-mark使用详解(跳过、标记、参数 化)
1.前言 在工作中我们经常使用pytest.mark.XXXX进行装饰器修饰,后面的XXX的不同,在pytest中有不同的作 用,其整体使用相对复杂,我们单独将其抽取出来做详细的讲解。 2.pytest.mark.skip()/skipif()跳过用例 import pytest #无条…...

【浅尝Java】Java简介第一个Java程序(含JDK、JRE与JVM关系、javcdoc的使用)
🍞自我激励:每天努力一点点,技术变化看得见 文章目录 Java语言概述Java是什么Java语言的重要性Java语言发展简史Java语言特性 第一个Java程序main方法示例运行Java程序JDK、JRE、JVM之间的关系注释基本规则注释规范 标识符关键字 Java语言概述…...
游戏打击感实现
视觉表现 1.帧冻结(卡肉) 原理:在攻击命中的瞬间暂停动画播放(通常0.1-0.3s),伯尼真实打击时的反作用力停滞感。实现:通过控制动画播放速度(如Unity的Animator.speed)结…...

项目三 - 任务2:创建笔记本电脑类(一爹多叔)
在本次实战中,我们通过Java的单根继承和多接口实现特性,设计了一个笔记本电脑类。首先创建了Computer抽象类,提供计算的抽象方法,模拟电脑的基本功能。接着定义了NetCard和USB两个接口,分别包含连接网络和USB设备的抽象…...

Electron学习+打包
1. 什么是 Electron? Electron 是⼀个 跨平台桌⾯应⽤ 开发框架,开发者可以使⽤:HTML、CSS、JavaScript 等 Web 技术来构建桌⾯应⽤程序,它的本质是结合了 Chromium 和 Node.js ,现在⼴泛⽤于桌⾯应 ⽤程序开发&a…...

NumPy线性代数功能全解析:矩阵运算与方程求解实用指南
NumPy 是线性代数领域中高效的工具。它可以帮助完成矩阵运算和方程求解。本文将介绍 NumPy 中用于线性代数的常用函数。 矩阵乘法 矩阵乘法会根据两个矩阵生成一个新矩阵。具体做法是将第一个矩阵的每一行与第二个矩阵的每一列相乘,并将乘积相加,得到新…...

《RabbitMQ 全面解析:从原理到实战的高性能消息队列指南》
一、RabbitMQ 核心原理与架构 1. 核心组件与工作流程 RabbitMQ 基于 AMQP 协议,核心组件包括 生产者(Producer)、交换机(Exchange)、队列(Queue) 和 消费者(Consumer)。…...
jenkins slave节点打包报错Failed to create a temp file on
jenkins slave节点打包报错 一、报错信息 FATAL: Unable to produce a script file Also: hudson.remoting.Channel$CallSiteStackTrace: Remote call to slave-83at hudson.remoting.Channel.attachCallSiteStackTrace(Channel.java:1784)at hudson.remoting.UserRequest$…...
计算机视觉——通过 OWL-ViT 实现开放词汇对象检测
介绍 传统的对象检测模型大多是封闭词汇类型,只能识别有限的固定类别。增加新的类别需要大量的注释数据。然而,现实世界中的物体类别几乎无穷无尽,这就需要能够检测未知类别的开放式词汇类型。对比学习(Contrastive Learning)使用成对的图像和语言数据,在这一挑战中备受…...