【数据结构与算法】回溯法解题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核以供后续使用 六、…

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

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

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

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

鸿蒙 渲染控制

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

Ps:绘画对称功能

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

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

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

Opencv基本操作 (上)

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

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

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

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

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

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

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

DOM 获取父子节点

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

【计算机网络——应用层】http协议

文章目录 1. http协议1.1 http协议简介1.2 url组成1.3 urlencode与urldecode 2. http协议的格式2.1 http协议的格式2.2 一些细节问题 3. http的方法、状态码和常见响应报头3.1 http请求方法3.2 http状态码3.3 http常见的响应报头属性 4. 一个非常简单的http协议服务端5. http长…

【X806开发板试用】文章一 ubuntu开发环境搭建

一、环境配置 官方链接: 环境配置 1.安装必要的库和软件 sudo apt-get install build-essential gcc g make zlib* libffi-dev e2fsprogs pkg-config flex bison perl bc openssl libssl-dev libelf-dev libc6-dev-amd64 binutils binutils-dev libdwarf-dev u-b…

pix2pix-zero

pix2pix-zero:零样本图像到图像转换 论文介绍 Zero-shot Image-to-Image Translation 关注微信公众号: DeepGoAI 项目地址:https://github.com/pix2pixzero/pix2pix-zero 论文地址:https://arxiv.org/abs/2302.03027 本文介绍了一种名为…

基于SpringBoot多模块项目引入其他模块时@Autowired无法注入

基于SpringBoot多模块项目引入其他模块时Autowired无法注入 一、问题描述1、解决方案 一、问题描述 启动Spring Boot项目时报 Could not autowire. No beans of ‘xxxxxxxx’ type found. 没有找到bean的实例,即spring没有实例化对象,也就无法根据配置文…

【LeetCode-中等】209.长度最小的子数组-双指针/滑动窗口

力扣题目链接 1. 暴力解法 这道题的暴力解法是两层嵌套for循环,第一层循环从 i 0 开始遍历至数组末尾,第二层循环从 j i 开始遍历至找到总和大于等于 target 的连续子数组,并将该连续子数组的长度与之前找到的子数组长度相比较&#xff0…

什么是IP公网?

IP公网是指互联网上可以公开访问的IP地址。它是经过互联网服务提供商(ISP)向用户提供的公共网络IP地址。与之相对的是内网IP地址,内网IP地址一般是由路由器或交换机分配给连接在局域网中的设备使用。 IP公网的作用非常广泛,可以应…

Web APIs知识点讲解(阶段二)

DOM-事件基础 一.事件 1.事件 目标:能够给 DOM元素添加事件监听 事件:事件是在编程时系统内发生的动作或者发生的事情,比如用户在网页上单击一个按钮 事件监听:就是让程序检测是否有事件产生,一旦有事件触发,就立即调用一个函…

金仕达与 DolphinDB 建立深度合作,共筑 FICC 科技创新新篇章

从“关起门做交易”到“打开门做服务”,国内 FICC 业务正经历从自营到市场化服务的转变,借助数据分析、算法交易等技术的快速发展,交易团队能够更加主动地发现市场需求,为不同客群提供更好的做市业务,FICC 交易电子化已…

打造智能汽车微服务系统平台:架构的设计与实现

随着智能汽车技术的飞速发展,微服务架构在汽车行业中的应用越来越广泛。采用微服务架构可以使汽车系统更加灵活、可扩展,并且有利于快速推出新功能和服务。本文将从设计原则、关键技术、数据安全等方面,介绍如何搭建智能汽车微服务系统平台架…