递归回溯实战+思想

news/2024/4/29 5:42:21/文章来源:https://blog.csdn.net/weixin_57128596/article/details/126925534

目录

排列(提供元素无重复,并且不可以重复选择)

排列(提供的元素重复了,但是同个位置的元素不能复选)

组合(提供的元素没有重复,并且可以重复选择相同位置元素)

子集(提供的元素没有重复,且同位置元素只能选择一次)

组合(提供元素不重复,且同位置不能重复选)


 

排列(提供元素无重复,并且不可以重复选择)

思路:1.根据题意找到base case——>2.然后遍历提供的数据节点,(不可重复选择同一数据节点,并且提供的数据节点都没有重复)——>3.我们利用boolean数据进行判断,当前数据节点是否选择,如果选择过直接continue——>4.没有选择过进行选择逻辑并且修改状态——>5.进入递归函数——>6.回溯,撤销逻辑选择

 模板:

/* 组合/子集问题回溯算法框架 */
void backtrack(int[] nums, int start) {// 回溯算法标准框架for (int i = start; i < nums.length; i++) {// 做选择track.addLast(nums[i]);// 注意参数backtrack(nums, i + 1);// 撤销选择track.removeLast();}
}/* 排列问题回溯算法框架 */
void backtrack(int[] nums) {for (int i = 0; i < nums.length; i++) {// 剪枝逻辑if (used[i]) {continue;}// 做选择used[i] = true;track.addLast(nums[i]);backtrack(nums);// 撤销选择track.removeLast();used[i] = false;}
}

例题:

46. 全排列 - 力扣(LeetCode)

 //1.结果集List<List<Integer>>res=new LinkedList<>();public List<List<Integer>> permute(int[] nums) {//1.记录一条路径LinkedList<Integer>track = new LinkedList<>();//2.记录每个元素状态boolean[] used=new boolean[nums.length];backtrack(nums,track,used);return res; }void backtrack(int[]nums,LinkedList<Integer>track,boolean[]used){//1.结束条件if(track.size()==nums.length){res.add(new LinkedList(track));return;}//2.遍历每个元素for(int i=0;i<nums.length;i++){//2.1排除不合法的选择if(used[i]){continue;}//2.2对当前元素做选择track.add(nums[i]);used[i]=true;backtrack(nums,track,used);//2.3进行回溯track.removeLast();used[i]=false;}}

排列(提供的元素重复了,但是同个位置的元素不能复选)

不同之处:和上述思路差不多,加了一个剪枝操作,因为提供的元素有重复(比如:【1,2,2】),所以我们需要固定重复元素的位置,防止出现重复结果

打个比方就以 nums = [1,2,2] 为例,为了区别两个 2 是不同元素,后面我们写作 nums = [1,2,2']

——>显然,两条值相同的相邻树枝会产生重复

 剪枝:需要先进行排序,让相同的元素靠在一起,如果发现 nums[i] == nums[i-1]&&used[i-1] (排列情况)

比如输入 nums = [1,2,2',2'']2' 只有在 2 已经被使用的情况下才会被选择,同理,2'' 只有在 2' 已经被使用的情况下才会被选择,这就保证了相同元素在排列中的相对位置保证固定

if (i > 0 && nums[i] == nums[i - 1] && used[i-1]) continue;

 模板:

Arrays.sort(nums);
/* 组合/子集问题回溯算法框架 */
void backtrack(int[] nums, int start) {// 回溯算法标准框架for (int i = start; i < nums.length; i++) {// 剪枝逻辑,跳过值相同的相邻树枝if (i > start && nums[i] == nums[i - 1]) {continue;}// 做选择track.addLast(nums[i]);// 注意参数backtrack(nums, i + 1);// 撤销选择track.removeLast();}
}Arrays.sort(nums);
/* 排列问题回溯算法框架 */
void backtrack(int[] nums) {for (int i = 0; i < nums.length; i++) {// 剪枝逻辑if (used[i]) {continue;}// 剪枝逻辑,固定相同的元素在排列中的相对位置if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {continue;}// 做选择used[i] = true;track.addLast(nums[i]);backtrack(nums);// 撤销选择track.removeLast();used[i] = false;}
}

例题:

47. 全排列 II - 力扣(LeetCode)

class Solution {List<List<Integer>> perResult = new LinkedList<>();LinkedList<Integer> perTrack = new LinkedList<>();boolean[] used;public List<List<Integer>> permuteUnique(int[] nums) {//1.先排序Arrays.sort(nums);used = new boolean[nums.length];//2.递归函数permuteUniqueHelper(nums);return perResult;}void permuteUniqueHelper(int[] nums) {//1.base:子路径的结束条件if (perTrack.size() == nums.length) {perResult.add(new LinkedList<>(perTrack));return;}//2.遍历nums所有数据for (int i = 0; i < nums.length; i++) {//2.1判断当前节点有没有使用过——>使用过就不能再次出现if (used[i]) continue;//2.2剪枝逻辑,固定排序位置if (i > 0 && nums[i] == nums[i - 1] && used[i-1]) continue;//2.3添加当前节点进子序列perTrack.add(nums[i]);used[i]=true;permuteUniqueHelper(nums);//2.4回溯perTrack.removeLast();used[i]=false;}}}

组合(提供的元素没有重复,并且可以重复选择相同位置元素)

一般作用于求和类组合题目上,像这种提供元素不重复(重复元素,要考虑去重->剪枝即可),可以重新选择(删除去重逻辑->start不+1)

39. 组合总和 - 力扣(LeetCode)

class Solution {List<List<Integer>> res = new LinkedList<>();
// 记录回溯的路径
LinkedList<Integer> track = new LinkedList<>();
// 记录 track 中的路径和
int trackSum = 0;public List<List<Integer>> combinationSum(int[] candidates, int target) {if (candidates.length == 0) {return res;}backtrack(candidates, 0, target);return res;
}// 回溯算法主函数
void backtrack(int[] nums, int start, int target) {// base case,找到目标和,记录结果if (trackSum == target) {res.add(new LinkedList<>(track));return;}// base case,超过目标和,停止向下遍历if (trackSum > target) {return;}// 回溯算法标准框架for (int i = start; i < nums.length; i++) {// 选择 nums[i]trackSum += nums[i];track.add(nums[i]);// 递归遍历下一层回溯树// 同一元素可重复使用,注意参数backtrack(nums, i, target);// 撤销选择 nums[i]trackSum -= nums[i];track.removeLast();}
}}

子集(提供的元素没有重复,且同位置元素只能选择一次)

思路:只能选择一次,每次进入递归的时候,利用一个变量+1(start+1)防止重复踩到相同位置元素——>1.因为是组合类题目,允许空集,所以前序位置直接结果集加一个子序列——>2.每次添加一个元素进入递归函数后——>结果集都会重新加一个子序列(也是通过start控制相同位置不重复访问的,利用track子序列添加元素,然后递归后慢慢撤销回溯,但是像我们以下的图,到i=2他就进不了1这个位置了)

模板:

List<List<Integer>> res = new LinkedList<>();
// 记录回溯算法的递归路径
LinkedList<Integer> track = new LinkedList<>();// 主函数
public List<List<Integer>> subsets(int[] nums) {backtrack(nums, 0);return res;
}// 回溯算法核心函数,遍历子集问题的回溯树
void backtrack(int[] nums, int start) {// 前序位置,每个节点的值都是一个子集res.add(new LinkedList<>(track));// 回溯算法标准框架for (int i = start; i < nums.length; i++) {// 做选择track.addLast(nums[i]);// 通过 start 参数控制树枝的遍历,避免产生重复的子集backtrack(nums, i + 1);// 撤销选择track.removeLast();}
}

 例题:

78. 子集 - 力扣(LeetCode)

class Solution {//1.结果集List<List<Integer>> result=new LinkedList<>();public List<List<Integer>> subsets(int[] nums) {//2.单节点路径LinkedList<Integer> track=new LinkedList<>();//3.递归函数back(nums,0,track);return result;}void back(int[]nums,int start,LinkedList<Integer>track){//1.首先添加一个空集合,对于后面就是添加有数据的节点result.add(new LinkedList<>(track));//2.回溯得到组合框架for(int i=start;i<nums.length;i++){//2.1添加节点track.addLast(nums[i]);//2.2进行递归back(nums,i+1,track);//2.3回溯track.removeLast();}}
}

组合(提供元素不重复,且同位置不能重复选)

思路:跟子集一样,

你比如说,让你在 nums = [1,2,3] 中拿 2 个元素形成所有的组合,你怎么做?

稍微想想就会发现,大小为 2 的所有组合,不就是所有大小为 2 的子集嘛。

所以我说组合和子集是一样的:大小为 k 的组合就是大小为 k 的子集

 

77. 组合 - 力扣(LeetCode)

class Solution {/*** 3.组合问题,找出给定数组中指定个数的组合** @param n:指定范围1-n* @param k:指定个数k个* @return*/List<List<Integer>> res = new LinkedList<>();
// 记录回溯算法的递归路径
LinkedList<Integer> track = new LinkedList<>();// 主函数
public List<List<Integer>> combine(int n, int k) {backtrack(1, n, k);return res;
}void backtrack(int start, int n, int k) {// base caseif (k == track.size()) {// 遍历到了第 k 层,收集当前节点的值res.add(new LinkedList<>(track));return;}// 回溯算法标准框架for (int i = start; i <= n; i++) {// 选择track.addLast(i);// 通过 start 参数控制树枝的遍历,避免产生重复的子集backtrack(i + 1, n, k);// 撤销选择track.removeLast();}
}}

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

进程关系~

进程关系一、进程组二、会话三、控制终端四、函数tcgetpgrp、tcsetpgrp和tcgetsid五、作业控制五、孤儿进程组一、进程组 每个进程除了有一进程ID之外&#xff0c;还属于一个进程组&#xff0c;进程组是一个或多个进程的集合。同一进程组中的各进程接收来自同一终端的各 种信号…

Eclipse2022创建SSM项目及问题解决

Eclipse2022创建SSM项目及问题解决 使用Eclipse创建SSM项目的过程中会遇到一些问题&#xff0c;相对于IDEA而言更为繁琐&#xff0c;该篇文章是在使用Eclipse2022&#xff0c;并且设备上已经安装、配置好了Tomcat和Maven的基础之上进行的&#xff0c;目的是为了记录在Eclipse上…

Windows部署JMeter的压力测试

1.安装Windows版本Java 直接下载Java exe格式程序包 官网下载 点击 2.下载JMeter的压缩包 官网下载地址请 点击 或者复制这个URL: https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.5.tgz 在浏览器上&#xff0c;会自动下载 下载下来解压即可。 3.启动JMeter 进入…

Linux内核设计与实现 第十二章 内存管理

因为内核内存需要节省着用&#xff0c;内核处理内存分配错误比较麻烦等&#xff0c;所以内核中获取内存不用户空间获取内存复杂得多。 本章讨论内核是如何管理内存和内核之中获取内存的办法。 12.1页 a) 可以通过 getconf 命令来查看系统的page的大小&#xff1a; [wangyubi…

Linux开发_CentOS7.4服务器安装NFS、NGINX服务器,ffmpeg、Qt环境

1. 环境介绍 环境介绍&#xff1a;采用的是华为云的ECS弹性云服务器–镜像安装的CentOS7.4 64位 -----是服务器版&#xff0c;非桌面版哦。 在CentOS7.4服务器版本的环境下搭建NFS服务器、安装ffmpeg、安装nginx服务器、部署Qt编译环境。 &#xff08;1&#xff09;配置NGIN…

河北稳控科技几种振弦采集仪的主要区别是什么?

河北稳控科技几种振弦采集仪的主要区别是什么?VH系列属于手持系列,多用于振弦传感器现场单次测量使用;VH501TC采集读数仪,设备是专用的多类型传感器手持式读数仪,主测传感类型为单弦式振弦传感器,辅测传感类型为电压、电流传感。采用 32 位 ARM 处理器和大尺寸全彩屏、阵…

无人机群编队分析的定位问题 分析与思考-1(数学建模竞赛2022年B题)

2022年高教社杯全国大学生数学建模竞赛结束了&#xff0c;在此我们对 2022年 B题 进行一些分析与思考。 1. 初步印象 2022年 B题 &#xff08;无人机遂行编队飞行中的纯方位无源定位&#xff09;是一个有趣的题目。 随着无人机技术的快速发展&#xff0c;早已从高科技变做寻常…

【Java】运算符

我不去想是否能够成功 既然选择了远方 便只顾风雨兼程 —— 汪国真 目录 1. 认识运算符 1.1 认识运算符 1.2 运算符的分类 2. 算术运算符 2.1 四则运算符 2.2 复合赋值运算符 2.3 自增 / 自减 运算符 3.关系运算符 4.逻辑运算符 4.1 逻辑与 && 4.2 逻…

分库分表实践

分库分表实践 分库分表概念以及使用场景 分库分表用来解决单表数据量太大&#xff0c;引起的性能问题。使用分库分表后能够根据特定路由键值将数据分布在不同库以及不同表中&#xff0c;解决了单表数据量的性能、运维等问题。一般来讲&#xff0c;单一数据库实例的数据的阈值…

【网络】HTTP协议详解

&#x1f600;大家好&#xff0c;我是白晨&#xff0c;一个不是很能熬夜&#x1f62b;&#xff0c;但是也想日更的人✈。如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下&#x1f440;白晨吧&#xff01;你的支持就是我最大的动力&#xff01;&#x1f4…

svn 代码迁入gitlab

window中安装好git客户端,右键空白处,点选git bash here进入git界面,输入命令 将svn38163之后的所有记录都备份那:git svn clone -r 38163:HEAD svn地址 --no-metadata trunk(本地电脑目录名) --username *** 备份所有提交记录:git svn clone svn地址 --no-metadata …

Linux安装Python 以及过程中的命令详细介绍

下载源码包 打开 Python 官网 找到需要的安装包 获取了资源的链接后&#xff0c;进入Linux下载&#xff0c;wget意思是webget&#xff0c; 即下载 wget https://www.python.org/ftp/python/3.10.7/Python-3.10.7.tgz目录下会新增 这样源码包就下载好了。 如果下载太慢&…

二叉树与递归问题

目录 一&#xff1a;求二叉树的深度 二&#xff1a;二叉树反转 三&#xff1a;二叉树镜像判断 四&#xff1a;递归的终止条件 用递归解决的问题必须注意的&#xff1a; 递归的终止条件&#xff0c;也就是递归的出口&#xff08;否则&#xff1a;栈溢出&#xff09;递归的过…

决策树简介

决策树简介 决策树实际上是一个布尔函数,它的输出可以是“0 或 1”或“-1 或 +1”或“-1、0 或 +1”。决策树的大小等于其中存在的节点数,其深度等于从顶部到根的最长路径的长度。 错误率:训练集始终是给模型的标记示例,模型训练得越多,其错误率就越低。 训练样本 = { set…

程序里对象很深很大,可以用这个设计模式缓解一下

如果一个类的有非常多的属性&#xff0c;层级还很深。这个妥妥的是我的对象很大&#xff0c;你创建的时候忍一下......那你每次要创建的时候都忍一下&#xff1f;有没有一种好的方式让我们创建太的时候使用体验更好一点呢? 今天的文章里就给大家介绍一种设计模式&#xff0c;来…

C++多线程的线程返回值问题

对于多线程可执行对象的返回值是何时返回&#xff0c;以及得到的呢&#xff1f; 对于需要用到线程返回值的线程要使用future类对象来实现 文章目录future对象async()launch::deferred参数launch::async参数packaged_taskpromisefuture对象 是一个类模板 提供访问异步对象的操作…

优化 | Management Science 7-8月文章精选: 信息系统中的运筹学

作者&#xff1a;Evelyn Yao 清华大学本科在读 在“Management Science近期论文精选”中&#xff0c;我们有主题、有针对性地选择了Management Science中一些有趣的文章&#xff0c;不仅对文章的内容进行了概括与点评&#xff0c;而且也对文章的结构进行了梳理&#xff0c;旨在…

非零基础自学Java (老师:韩顺平) 第13章 常用类 13.5 StringBuffer类

非零基础自学Java (老师&#xff1a;韩顺平) ✈【【零基础 快速学Java】韩顺平 零基础30天学会Java】 第13章 常用类 文章目录非零基础自学Java (老师&#xff1a;韩顺平)第13章 常用类13.5 StringBuffer类13.5.1 基本介绍13.5.2 String VS StringBuffer13.5.3 String 和 Str…

HashMap

1.HashMap集合 1.1HashMap集合概述和特点【理解】 HashMap底层是哈希表结构的依赖hashCode方法和equals方法保证键的唯一如果键要存储的是自定义对象&#xff0c;需要重写hashCode和equals方法 1.2 特点 HashMap是线程不安全的实现&#xff1b; HashMap可以使用null作为key…

【Pytorch深度学习实战】(9)神经语言模型(RNN-LM)

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…