回溯算法(递归+回退)——1基础理论

文章目录

    • 一、概念
    • 二、算法原理
    • 三、代码模板
    • 四、例题实现
      • 1、参数确定
      • 2、确定终止条件
      • 3、for循环的构建
      • 4、AC代码
        • Java
        • C++
      • 5、剪枝优化
        • 理论:
        • 代码编写方式:
        • Java
        • C++

一、概念

回溯算法(BackTracking)一种通过递归,实现暴力枚举得出答案的算法。
你没看错,就是递归+暴力枚举,所以他的算法效率并不高。那么有些小伙伴可能就会问了:关于暴力我已经有for循环这个爸爸了,为什么还要学回溯呢,这不是多此一举???

这个问题提得好!虽然回溯算法看起来效率并不高(当然效率的确不高😂,因为还增加了递归),但是在应对一些复杂问题的时候,我们只能通过回溯来完成,如果使用for循环写会非常麻烦!
对于某些题目,你甚至根本写不出通过for循环实现的枚举!

例如这道题目:
在这里插入图片描述
题目链接
注意:组合和排列是两个不同的概念: 组合不强调元素的前后顺序,排列反之。 例如[1,2]和[2,1]被认为是同一个组合,但[1,2]和[2,1]是两个不同的排列!

倘若按照题中得示例一(n=4,k=2),用for循环还是比较好写的:
在这里插入图片描述
如图代码,只要写两个for循环,就可以找到所有两个数字的组合。
但是如果题目给的参数n=1,000 k=100呢?
难道写100个for循环???
显然通过for循环来实现枚举已经是现实的了,我们需要通过回溯法来解决这道题目。

我们先不着急解决这一道题目,先接着往下看。

二、算法原理

上一节讲到,回溯法本质就是通过递归枚举所有的可能,返回符合情况的结果。
那么回溯法是如何通过递归,枚举所有的可能呢?他为什么这种算法叫回溯,回溯在哪里?
接下来,我会以上一节的例题为例,统一回答这些问题:

在此之前,我们需要了解递归这三个知识:
1、递归程序需要设置一个终止条件,否则就会死循环。
2、任何递归程序的运行过程,可以通过一个树形图来表示,也就是N叉树。
3、想要写出递归,我们要寻找出这个问题的重复子问题,因为递归就是把一个大问题拆分成小的、重复的子问题。

还是以n=4 k=2为例,回溯算法解决这个问题的过程可以形象的表示成这一个树形图:
注意:回溯法通过前序遍历(root \ left \ right)这个N叉树,寻找可能的答案,所以这张图你需要通过前序遍历的方式去观看
在这里插入图片描述

图片解析:
1、我们看到,每次递归,都会选取候选集合中的一个数字,放到已选集合中这就是递归的子问题。
2、递归到第三层的叶子结点后,由于已选集合大小==k,所以终止搜索,向不在向下递归,这就是递归的终止条件。
3、图中的回退操作,就是所谓的回溯。 在搜索遍历到叶子节点后,由于递归的性质,会逐层回退,当前结点中的状态会转变成上一层结点原来的状态。这样做的目的是让每一次枚举的结果都不会被上一次枚举的结果影响。

三、代码模板

理论讲完了,看起来好像有点复杂,但是实际上代码的编写还是比较简单的。
不论什么题目,只要使用回溯算法,代码风格都比较的固定:

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

回溯函数定义的三个要点:
1、确定参数、返回值(返回值一般是void)
2、确定递归终止条件
3、构建合适的for循环

四、例题实现

回溯的套路比较固定,就是构建一个递归函数,一般分为三步:

1、参数确定

n: 表示需要选取的数字范围是1-n
k: 表示每一个组合的到小是k
curIndex: 由于我们需要确保不会枚举到重复的组合,所以我们用一个指针curIndex表示当前选择的数组(从左到右移动)。

2、确定终止条件

第二节的图片解析说明了,搜索的返回条件就是找到k个元素的符合条件的组合就回退,枚举下一个可能。

3、for循环的构建

依据N叉树的示意图,我们只需要从1到N进行for循环,在每一次循环再次调用backTracking(参数)即可。
注意:for循环的具体编写方式不同题目,可能不太一样,需要通过刷题来找感觉。

4、AC代码

Java
class Solution {//存放满足条件的组合 ans是一个列表,列表中存储着满足条件的所有组合List<List<Integer>> ans=new ArrayList<>();//用来存储可能满足条件的一个组合(已选数字)List<Integer> path=new ArrayList<>();public List<List<Integer>> combine(int n, int k) {//1、确定参数backTracking(n,k,1);return ans;//记得返回答案}private void backTracking(int n,int k,int curIndex){//2、终止条件:找到k个元素的组合就返回if(path.size()==k){//记得先把答案塞到答案集ans.add(new ArrayList<>(path));return;}//for循环的构建for(int i=curIndex;i<=n;i++){//尝试枚举path.add(i);backTracking(n,k,i+1);//这里必须curIndex+1 避免枚举到重复的组合//在递归完成后,根据N叉树示意图,必须还原已选数字!!(回溯)path.remove(path.size()-1);//这也是回溯 区别于普通递归的地方}        }}
C++
class Solution {
private:vector<vector<int>> result;vector<int> path;void backtracking(int n, int k, int curIndex) {if (path.size() == k) {result.push_back(path);return;}for (int i = curIndex; i <= n; i++) {path.push_back(i); // 处理节点backtracking(n, k, i + 1);path.pop_back(); // 回溯,撤销处理的节点}}
public:vector<vector<int>> combine(int n, int k) {backtracking(n, k, 1);return result;}
};

5、剪枝优化

理论:

如果把回溯法通过N叉树来理解,对于这道例题,N叉树的某些枝节是没有必要遍历的,可以直接剪短(选4的这个结点):
在这里插入图片描述
那么为什么可以剪断这个枝节呢?
很简单,在选择完4之后,就不能在往后面选了呀。如果返回去选1、2、3那么就必定会与之前的枝节发生重复,导致答案重复。

代码编写方式:

根据优化的原理,我们只需要对代码中for循环的边界做一个限制即可:
在这里插入图片描述

修改成具体什么值,只用关注这几个变量之间的关系:

  1. 已选集合的大小:path.size()
  2. 候选集合的大小:n
  3. 要求组合的大小: k
  4. 当前指针指向的数字:i

k - path.size() (要求组合的大小-已选集合的大小)= 剩余需要选择的元素数量记作a
另外,n - i = 实际剩余可选的值

这里其实有一个小问题:
在这里插入图片描述
所以正确的推导应该是:n - i+1 = 实际剩余可选的值(记作A)

那么如果要避免无意义的搜索,必须满足这样一个大小关系:A>=a
即:n-i+1>=k-path.size()=》i<=n-(k-path.size())+1

Java
class Solution {List<List<Integer>> ans=new ArrayList<>();List<Integer> path=new ArrayList<>();public List<List<Integer>> combine(int n, int k) {backTracking(n,k,1);return ans;}private void backTracking(int n,int k,int curIndex){if(path.size()==k){ans.add(new ArrayList<>(path));return;}for(int i=curIndex;i<=n-(k-path.size())+1;i++){//唯一优化的地方path.add(i);backTracking(n,k,i+1);path.remove(path.size()-1);}}
}
C++
class Solution {
private:vector<vector<int>> result;vector<int> path;void backtracking(int n, int k, int curIndex) {if (path.size() == k) {result.push_back(path);return;}for (int i = curIndex; i <=n-(k-path.size())+1; i++) { //唯一优化的地方path.push_back(i); backtracking(n, k, i + 1);path.pop_back(); }}
public:vector<vector<int>> combine(int n, int k) {backtracking(n, k, 1);return result;}
};

参考资料:代码随想录

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

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

相关文章

Python | Leetcode Python题解之第429题N叉树的层序遍历

题目&#xff1a; 题解&#xff1a; class Solution:def levelOrder(self, root: Node) -> List[List[int]]:if not root:return []ans list()q deque([root])while q:cnt len(q)level list()for _ in range(cnt):cur q.popleft()level.append(cur.val)for child in c…

【数据结构与算法】LeetCode:二分查找

文章目录 二分查找二分查找搜索插入位置 &#xff08;Hot 100&#xff09;x 的平方根搜索二维矩阵&#xff08;Hot 100&#xff09;在排序数组中查找元素的第一个和最后一个位置 &#xff08;Hot 100&#xff09;搜索旋转排序数组 &#xff08;Hot 100&#xff09;寻找旋转排序…

postman工具

postman是什么接口工具。接口是什么api&#xff08;俗称应用编程接口&#xff0c;简称接口&#xff09;&#xff1b;也就是程序&#xff08;服务端程序&#xff09;与程序&#xff08;客户端程序&#xff09;之间的通信方式。例如模仿服务端发送请求到客户端例如模仿客户端发送…

情指行一体化平台建设方案和必要性-———未来之窗行业应用跨平台架构

一、平台建设必要性 以下是情指行一体化平台搭建的一些必要性&#xff1a; 1. 提高响应速度 - 实现情报、指挥和行动的快速协同&#xff0c;大大缩短从信息获取到决策执行的时间&#xff0c;提高对紧急情况和突发事件的响应效率。 2. 优化资源配置 - 整合各类资源信…

没有 Microsoft Wi-Fi Direct Virtual Adapter #2 导致无法打开热点

我的环境 电脑打不开热点 系统 win11 64位 品牌 hp 笔记本电脑 解决方法&#xff1a; https://answers.microsoft.com/zh-hans/windows/forum/all/%E7%A7%BB%E5%8A%A8%E7%83%AD%E7%82%B9%E6%97%A0/9285620a-71d9-4671-b125-4cd607b6371a 解决 &#x1f613; 扫描一下设…

Codeforces Round 969 (Div. 1) C. Eri and Expanded Sets(线段树维护差分数组gcd+双指针+尺取)

题目 转化一下题意就是&#xff0c; 给定一个n(n<4e5)&#xff0c;代表数组a的长度&#xff0c; 求有多少区间&#xff0c;满足区间内两两差分后得到的新数组的gcd∈{0,1} 实际t(t<1e4)组样例&#xff0c;保证sumn不超过4e5 思路来源 乱搞acjiangly代码 题解 一个…

摆脱困境并在 Android 手机上取回删除照片的所有解决方案

没有什么比不小心从 Android 智能手机中删除所有照片更糟糕的了。这样&#xff0c;除非您在重置之前已经备份了数据&#xff0c;否则您的所有照片都会消失。如果您忘记备份照片&#xff0c;您仍然可以按照一些简单的技术在 Android 设备上恢复已删除的照片。 您的 Android 智能…

【漏洞复现】用友 NC-Cloud queryStaffByName Sql注入漏洞

免责声明&#xff1a; 本文内容旨在提供有关特定漏洞或安全漏洞的信息&#xff0c;以帮助用户更好地了解可能存在的风险。公布此类信息的目的在于促进网络安全意识和技术进步&#xff0c;并非出于任何恶意目的。阅读者应该明白&#xff0c;在利用本文提到的漏洞信息或进行相关测…

VMware安装ubuntu24.04桌面版

一、安装推荐要求 双核2 GHz处理器或更高 4 GB系统内存 25 GB磁盘存储空间 可访问的互联网 光驱或USB安装介质 二、下载桌面系统 下载地址&#xff08;使用手机转存再下载是对作者的最大支持&#xff09;&#xff1a;夸克网盘分享 (quark.cn) 已安装的纯净版ubuntu虚拟…

招联金融2025秋招--大量招后台、算法

【投递方式】 直接扫下方二维码&#xff0c;或点击内推官网https://wecruit.hotjob.cn/SU61025e262f9d247b98e0a2c2/mc/position/campus&#xff0c;使用内推码 igcefb 投递 【招聘岗位】 后台开发 前端开发 数据开发 数据运营 算法开发 技术运维 软件测试 产品策划 产品运营…

Day05 日期类OJ题目

计算日期到天数转换_牛客题霸_牛客网根据输入的日期&#xff0c;计算是这一年的第几天。 保证年份为4位数且日期合法。 进阶&#xff1a;时。题目来自【牛客题霸】https://www.nowcoder.com/share/jump/4938575031726974727572 根据输入的日期&#xff0c;计算是这一年的第几…

Golang | Leetcode Golang题解之第429题N叉树的层序遍历

题目&#xff1a; 题解&#xff1a; func levelOrder(root *Node) (ans [][]int) {if root nil {return}q : []*Node{root}for q ! nil {level : []int{}tmp : qq nilfor _, node : range tmp {level append(level, node.Val)q append(q, node.Children...)}ans append(a…

HTML和CSS做一个无脚本的手风琴页面(保姆级)

一、前言 使用HTML和CSS做一个无脚本的手风琴页面。让知识以自己喜欢的方式进入脑子&#xff0c;适用于很多场景&#xff0c;比如以下&#xff1a; 【注&#xff1a;图片源自百度】 二、HTML框架 使用外部样式表&#xff0c;将CSS文件用link标签引入 整体框架如下图&#x…

20240923 每日AI必读资讯

GPT-4o能玩《黑神话》&#xff01;精英怪胜率超人类&#xff0c;无强化学习纯大模型方案 - 阿里巴巴的研究人员们提出了一个新型VARP&#xff08;视觉动作角色扮演&#xff09;智能体框架。 - 能直接将游戏截图作为输入&#xff0c;通过视觉语言模型推理&#xff0c;最终生成…

WebGL颜色与纹理

WEBGL中的着色器变量包括以下种类&#xff1a; 属性变量&#xff08;Attribute Variables&#xff09;&#xff1a;这些变量用于接收从应用程序中传递的顶点数据&#xff0c;比如顶点位置和颜色&#xff0c;是只读的不可修改。统一变量&#xff08;Uniform Variables&#xff…

STM32篇:开发环境安装

编程语言&#xff1a;C语言 需要安装的软件有两个&#xff1a;Keil5 和 STM32CubeMX 一.Keil5 的安装 使用 Keil4 写 STM32 代码其实也是可以&#xff0c;但需要很复杂的配置&#xff0c;不建议新手操作。 比较推荐 Keil5 编写 STM32 &#xff0c;只需要一些简单的设置就可…

定了,东湖高新区下半年中高级职称申报时间

2024年东湖高新区中级职称申报开始了&#xff0c;水测成绩上周已出&#xff0c;本周已经开始申报了&#xff0c;时间确实挺着急的。 报送材料起止时间及地点&#xff1a; &#xff08;一&#xff09;网上报名时间&#xff1a; 2024年9月14日至9月30日24:00 &#xff08;二&…

C++ | Leetcode C++题解之第429题N叉树的层序遍历

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<vector<int>> levelOrder(Node* root) {if (!root) {return {};}vector<vector<int>> ans;queue<Node*> q;q.push(root);while (!q.empty()) {int cnt q.size();vector<…

C/C++中的内存管理

文章目录 前言一、C/C中的内存分布二、C语言中动态内存管理方式&#xff1a;malloc/calloc/realloc/free 三、C中的内存管理方式四、operator new与operator delete函数五、new和delete的实现原理 六、定位new表达式(placement-new) 七、malloc/free和new/delete的区别 总结 前…

django学习入门系列之第十点《A 案例: 员工管理系统15》

文章目录 15 认识Ajax15.4 ajax请求的返回值实例2&#xff1a;前端输入数据提交到后端实例3&#xff1a;传输多个数据 往期回顾 15 认识Ajax 15.4 ajax请求的返回值 一般数据交互整合的都是json格式 后端一般会返回一个JSON格式 返回json格式一般有以下两种写法 上面注释的…