【数据结构与算法】回溯法解题20240301

在这里插入图片描述


这里写目录标题

  • 一、78. 子集
    • 1、nums = [1,2,3]为例把求子集抽象为树型结构
    • 2、回溯三部曲
  • 二、90. 子集 II
    • 1、本题搜索的过程抽象成树形结构如下:
  • 三、39. 组合总和
    • 1、回溯三部曲
    • 2、剪枝优化
  • 四、LCR 082. 组合总和 II
    • 1、思路
    • 2、树形结构如图所示:
    • 3、回溯三部曲

一、78. 子集

中等
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

示例 2:
输入:nums = [0]
输出:[[],[0]]

1、nums = [1,2,3]为例把求子集抽象为树型结构

在这里插入图片描述
从图中红线部分,可以看出遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合。

如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!
其实子集也是一种组合问题,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。
那么既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!

2、回溯三部曲

1、递归函数参数
全局变量数组path为子集收集元素,二维数组result存放子集组合。(也可以放到递归函数参数里)
递归函数参数在上面讲到了,需要startIndex。

剩余集合为空的时候,就是叶子节点。
那么什么时候剩余集合为空呢?
就是startIndex已经大于数组的长度了,就终止了,因为没有元素可取了,代码如下:

class S79:def func(self,nums):result=[]def dfs(path,startIndex):result.append(path[:])  #todo 收集结果代码为什么放到这里?因为每进入一层递归需要把当前结果放入resultif startIndex>=len(nums):returnfor i in range(startIndex,len(nums)):path.append(nums[i])dfs(path,i+1)       #todo i+1:保证之前传入的数,不再重复使用path.pop()dfs([],0)return resultr=S79()
nums=[1,2,3]
print(r.func(nums))

二、90. 子集 II

中等
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

示例 1:
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

示例 2:
输入:nums = [0]
输出:[[],[0]]

1、本题搜索的过程抽象成树形结构如下:

在这里插入图片描述
从图中可以看出,同一树层上重复取2 就要过滤掉,同一树枝上就可以重复取2,因为同一树枝上元素的集合才是唯一子集!

startIndex的目的是不再取当前数之前的数,防止重复 [2,1]——》[1,2]重复了

class S90:def func(self,nums):result=[]def dfs(path,used,startIndex):result.append(path[:])if len(nums)==len(path):returnfor i in range(startIndex,len(nums)):if i>0 and nums[i]==nums[i-1] and used[i-1]==False:continueif used[i]==True:continueused[i]=Truepath.append(nums[i])dfs(path,used,i+1)used[i]=Falsepath.pop()dfs([],[False]*len(nums),0)return resultr=S90()
nums=[1,2,2]
print(r.func(nums))

三、39. 组合总和

中等
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

示例 2:
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:
输入: candidates = [2], target = 1
输出: []

1、回溯三部曲

1、递归函数参数
这里依然是定义两个全局变量,二维数组result存放结果集,数组path存放符合条件的结果。(这两个变量可以作为函数参数传入)

首先是题目中给出的参数,集合candidates, 和目标值target。

此外我还定义了int型的sum变量来统计单一结果path里的总和,其实这个sum也可以不用,用target做相应的减法就可以了,最后如何target==0就说明找到符合的结果了,但为了代码逻辑清晰,我依然用了sum。

本题还需要startIndex来控制for循环的起始位置,对于组合问题,什么时候需要startIndex呢?
我举过例子,如果是一个集合来求组合的话,就需要startIndex;
如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex

2、递归终止条件
在如下树形结构中:
在这里插入图片描述

从叶子节点可以清晰看到,终止只有两种情况,sum大于target和sum等于target。
sum等于target的时候,需要收集结果,代码如下:

if total > target:return
if total == target:result.append(path[:])return

3、单层搜索的逻辑

单层for循环依然是从startIndex开始,搜索candidates集合。
本题元素为可重复选取的。
如何重复选取呢,代码注释

for i in range(startIndex, len(candidates)):total += candidates[i]path.append(candidates[i])dfs(total, i, path)		# 不用i+1了,表示可以重复读取当前的数total -= candidates[i]path.pop()

总代码:

class S39:def func(self, candidates, target):result = []def dfs(total, startIndex, path):if total > target:returnif total == target:result.append(path[:])returnfor i in range(startIndex, len(candidates)):total += candidates[i]path.append(candidates[i])dfs(total, i, path)total -= candidates[i]path.pop()dfs(0, 0, [])return resultr = S39()
candidates = [2,3,6,7]
target = 7
print(r.func(candidates, target))

2、剪枝优化

在这里插入图片描述
以及上面的版本一的代码大家可以看到,对于sum已经大于target的情况,其实是依然进入了下一层递归,只是下一层递归结束判断的时候,会判断sum > target的话就返回。

其实如果已经知道下一层的sum会大于target,就没有必要进入下一层递归了。
那么可以在for循环的搜索范围上做做文章了。
对总集合排序之后,如果下一层的sum(就是本层的 sum + candidates[i])已经大于target,就可以结束本轮for循环的遍历。

在这里插入图片描述
for循环剪枝代码如下:

for i in range(startIndex, len(candidates)):if total+candidates[i]>target:continuetotal += candidates[i]path.append(candidates[i])dfs(total, i, path)total -= candidates[i]path.pop()

总代码

class S39:def func(self, candidates, target):result = []def dfs(total, startIndex, path):# if total > target:#     returnif total == target:result.append(path[:])returnfor i in range(startIndex, len(candidates)):if total+candidates[i]>target:continuetotal += candidates[i]path.append(candidates[i])dfs(total, i, path)total -= candidates[i]path.pop()dfs(0, 0, [])return resultr = S39()
candidates = [2,3,6,7]
target = 7
print(r.func(candidates, target))

四、LCR 082. 组合总和 II

中等
给定一个可能有重复数字的整数数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次,解集不能包含重复的组合。

示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]

1、思路

这道题目和39.组合总和如下区别:
本题candidates 中的每个数字在每个组合中只能使用一次。
本题数组candidates的元素是有重复的,而39.组合总和是无重复元素的数组candidates
最后本题和39.组合总和要求一样,解集不能包含重复的组合。

本题的难点在于区别2中:集合(数组candidates)有重复元素,但还不能有重复的组合。

2、树形结构如图所示:

在这里插入图片描述

3、回溯三部曲

a、递归函数参数
与39.组合总和套路相同,此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。
这个集合去重的重任就是used来完成的。

b、递归终止条件
与39.组合总和相同,终止条件为 sum > target 和 sum == target。。
c、单层搜索的逻辑
这里与39.组合总和最大的不同就是要去重了。

前面我们提到:要去重的是“同一树层上的使用过”,如何判断同一树层上元素(相同的元素)是否使用过了呢。
如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]。
此时for循环里就应该做continue的操作。

在这里插入图片描述
我在图中将used的变化用橘黄色标注上,可以看出在candidates[i] == candidates[i - 1]相同的情况下:

used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
used[i - 1] == false,说明同一树层candidates[i - 1]使用过

可能有的录友想,为什么 used[i - 1] == false 就是同一树层呢,因为同一树层,used[i - 1] == false 才能表示,当前取的 candidates[i] 是从 candidates[i - 1] 回溯而来的。

而 used[i - 1] == true,说明是进入下一层递归,去下一个数,所以是树枝上,如图所示:
在这里插入图片描述

class LCR082:def func(self, candidates, target):candidates.sort()result = []def dfs(total, path, used, startIndex):if total == target:result.append(path[:])returnfor i in range(startIndex, len(candidates)):if total + candidates[i] > target:continueif i > 0 and candidates[i] == candidates[i - 1] and used[i - 1] == False:continueif used[i] == True:continueused[i] = Truetotal += candidates[i]path.append(candidates[i])dfs(total, path, used, i + 1)total -= candidates[i]path.pop()used[i]=Falsedfs(0, [], [False] * len(candidates), 0)return resultr = LCR082()
candidates = [10, 1, 2, 7, 6, 1, 5]
target = 8
print(r.func(candidates, target))

在这里插入图片描述

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

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

相关文章

用vivado创建一个赛灵思AXI的IP核

一、新建一个管理IP的任务 二、设置板子,verilog语言和文件位置 三、创建新的IP核 添加一个axi-full的master接口和axi-full的slave接口 四、查看赛灵思AXI代码 第一个是axi的master接口代码,下面的是axi的slave接口代码 五、打包IP核以供后续使用 六、…

共享旅游卡:打开0费用旅游新纪元,探索40+精彩线路

随着现代生活节奏的加快,旅游成为了许多人释放压力、寻求乐趣的方式。然而,面对琳琅满目的旅游线路和不断上涨的旅游费用,许多人望而却步。 今天,我们要为您介绍一种颠覆传统旅游方式的创新产品——共享旅游卡。它不仅能让您以0费…

什么是双线服务器?

双线服务器是一种有着两条高速网络线路的主机服务器,通常又被称为双线独享服务器,双线服务器的出现提高了服务器的可靠性,因为双线服务器对数据与请求可以使用两条高速网络线路进行处理,对比于单线服务器,提高了服务器…

easyexcel字体加粗

public static void main(String[] args) { List dataList new ArrayList<>(); dataList.add(new Data(“Data 1”)); dataList.add(new Data(“Data 2”)); dataList.add(new Data(“Data 3”)); // 设置加粗字体WriteCellStyle boldCellStyle new WriteCellStyle();W…

出现 ‘vue‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件的解决方法(图文界面)

目录 前言1. 问题所示2. 原理分析3. 解决方法前言 由于Java转全栈,对此前端的细节点都比他人更加注意,所以此处记录更有用的信息!(小白都能看懂) 1. 问题所示 出现如下问题: F:\vue_project>vue -version vue 不是内部或外部命令,也不是可运行的程序 或批处理文件…

基于Python的电商评论数据采集与分析|电商API接口数据采集

引言 在电商竞争日益激烈的情况下&#xff0c;商家既要提高产品质量&#xff0c;又要洞悉客户的想法和需求&#xff0c;关注客户购买商品后的评论&#xff0c;而第三方商家获取商品评价主要依赖于人工收集&#xff0c;不但效率低&#xff0c;而且准确度得不到保障。通过使用Py…

鸿蒙 渲染控制

前提&#xff1a;基于官网3.1/4.0文档。参考官网文档 基于Android开发体系来进行比较和思考。&#xff08;或有偏颇&#xff0c;自行斟酌&#xff09; 1.概念 ArkUI通过自定义组件的build()函数和builder装饰器中的声明式UI描述语句构建相应的UI。在声明式描述语句中开发者除了…

Ps:绘画对称功能

Photoshop 中的绘画对称 Paint Symmetry功能允许用户在画布上创建对称的绘画和设计&#xff0c;极大地提高了创作的效率和准确性&#xff0c;尤其适合于制作复杂的对称图形和图案。 可在使用画笔工具、铅笔工具或橡皮擦工具时启用“绘画对称"功能。 提示&#xff1a; 绘画…

Ubuntu Qt控制终端运行ros

文章目录 gnome-terminalQt 通过QProcess类Qt 通过system gnome-terminal 在Ubuntu中可以使用man gnome-terminal命令查看gnome-terminal的使用指南&#xff0c;也可在ubuntu manuals查看&#xff1a; NAMEgnome-terminal — 一个终端仿真应用.概要gnome-terminal [-e, --c…

Cocos游戏开发中的金币落袋效果

引言 Cocos游戏开发中的金币落袋效果 大家好,不知道大家有没有被游戏中的一些小细节打动或吸引。 往往游戏就是通过一些与众不同的细节,去留住玩家。 金币落袋效果正是如此,它比普通的数值变化来得更加形象,给予玩家成就感和满足感。 本文重点给大家介绍一下如何在Coc…

深入探索Java集合框架

在Java编程中&#xff0c;数据的组织和存储是核心部分。为了更有效地管理和操作这些数据&#xff0c;Java提供了一个强大且灵活的集合框架&#xff08;Java Collection Framework&#xff0c;JCF&#xff09;。这个框架不仅简化了数据结构的处理&#xff0c;还提供了高效的性能…

Opencv基本操作 (上)

目录 图像基本操作 阈值与平滑处理 图像阈值 图像平滑处理 图像形态学操作 图像梯度计算 Sobel 算子 Canny 边缘检测 图像金字塔与轮廓检测 图像轮廓 接口定义 轮廓绘制 轮廓特征与相似 模板匹配 傅里叶变换 傅里叶变换的作用 滤波 图像基本操作 读取图像&…

GDPU 算法分析与设计 天码行空 1

实验1 排序算法的效率分析 一、【实验目的】 &#xff08;1&#xff09;复习排序算法的实现过程&#xff1b; &#xff08;2&#xff09;设计平均与最坏情况下时间复杂度的数据环境并理解相关含义&#xff1b; &#xff08;3&#xff09;初步了解算法时间复杂度的分析方法。…

【Maven】Maven 基础教程(二):Maven 的使用

《Maven 基础教程》系列&#xff0c;包含以下 2 篇文章&#xff1a; Maven 基础教程&#xff08;一&#xff09;&#xff1a;基础介绍、开发环境配置Maven 基础教程&#xff08;二&#xff09;&#xff1a;Maven 的使用 &#x1f60a; 如果您觉得这篇文章有用 ✔️ 的话&#…

Qt中关于信号与槽函数的思考

信号与槽函数的思考 以pushbutton控件为例&#xff0c;在主界面上放置一个pushbutton控件&#xff0c;点击右键选择关联槽函数&#xff0c;关联一个click函数&#xff0c;如下图所示&#xff1a; 在该函数中&#xff0c;实现了一个点击pushbutton按钮后&#xff0c;弹出一个窗…

nginx使用详解--反向代理

什么是反向代理&#xff1f; 正向代理&#xff1a; 一般的访问流程是客户端直接向目标服务器发送请求并获取内容&#xff0c;使用正向代理后&#xff0c;客户端改为向代理服务器发送请求&#xff0c;并指定目标服务器&#xff08;原始服务器&#xff09;&#xff0c;然后由代理…

在极狐GitLab 配置 SSL/https

本文作者 徐晓伟 说明 极狐GitLab https 使用的是 nginx 实现的本文使用的域名是IP 192.168.80.14&#xff08;原因&#xff1a;如果使用域名&#xff0c;必须拥有这个域名的所有权&#xff0c;并增加解析才可以&#xff0c;要不然在 Docker 容器中&#xff0c;无法使用域名检…

go并发模式之----使用时顺序模式

常见模式之二&#xff1a;使用时顺序模式 定义 顾名思义&#xff0c;起初goroutine不管是怎么个先后顺序&#xff0c;等到要使用的时候&#xff0c;需要按照一定的顺序来&#xff0c;也被称为未来使用模式 使用场景 每个goroutine函数都比较独立&#xff0c;不可通过参数循环…

DOM 获取父子节点

DOM 是以树状结构排列的&#xff0c;所以父子关系是相对的&#xff0c;当li为我们的目标节点的时候&#xff0c;ul为其父节点&#xff0c;其他li为它的兄弟节点&#xff0c;li里面包含的标签为子节点&#xff0c;以此类推。 那我们如何找父节点&#xff1f; 元素.parentNode&am…

libigl 网格质量矩阵

文章目录 一、简介二、应用三、实现效果参考资料一、简介 在 libigl 中,igl::massmatrix 是一个用于计算给定三角网格的质量矩阵的函数。质量矩阵在有限元分析和其他模拟技术中非常有用,它通常用于描述网格中各个节点的质量或者用于计算模拟过程中的惯性效应。 igl::massmatr…