dfs刷题排列问题 + 子集问题 + 组和问题总结
文章目录
- 一、排列问题
- 全排列II
- 题解
- 代码
- 优美的排列
- 题解
- 代码
- 二、子集问题
- 字母大小写全排列
- 题解
- 代码
- 找出所有子集的异或总和再求和
- 题解
- 代码
- 三、组合问题
- 电话号码的字母组合
- 题解
- 代码
- 括号生成
- 题解
- 代码
- 组合
- 题解
- 代码
- 目标和
- 题解
- 代码
- 组合总和
- 题解
- 代码
- 总结
一、排列问题
全排列II
题目链接

题解
1. 这题和全排列那题框架是一样的,就是剪枝操作不一样
2. 同一节点出现相同元素肯定会重复,所以把同一节点的相同元素剪掉
3. 同一个数只能出现一次,用check数组剪枝
分为两种情况进行剪枝:
1、只关心不合法的分支:
不合法的进行跳过(剪枝)
check[i] == true || ( i != 0 &&nums[i] == nums[i-1] && check[i-1] == false)
这个点是已经使用过的,或者是这个点和前一个点是相同的并且前一个点没有使用过,i != 0保证不越界
2、只关心合法的分支:
合法的分支才进行dfs
check[i] == false && (i == 0 || nums[i] != nums[i-1] || check[i] == true)
这个点没有被使用过并且该点为第一个点肯定可以进行dfs,或者是该点和前一个点不相同也可以dfs,或者是该点和前一个点相同,但是前一个点上一层已经使用过了,这个点这层可以继续使用,因为它们是用的不同位置

代码
class Solution
{
public:vector<vector<int>> ret;vector<int> path;bool check[9];vector<vector<int>> permuteUnique(vector<int>& nums) {sort(nums.begin(),nums.end());dfs(nums);return ret;}void dfs(vector<int> nums){if(path.size() == nums.size()){ret.push_back(path);return;}// 如何把重复的数字剪掉for(int i = 0;i < nums.size();i++){// 合法的剪枝,不合法就不进行dfs// if((check[i] == false)&&// (i == 0||nums[i] != nums[i-1]||check[i-1] == true))// {// check[i] = true;// path.push_back(nums[i]);// dfs(nums);// // 恢复现场// check[i] = false;// path.pop_back();// }// 考虑不合法的剪枝,跳过不合法的剪枝if((check[i] == true)||(i != 0&&nums[i] == nums[i-1]&&check[i-1] == false))continue;check[i] = true;path.push_back(nums[i]);dfs(nums);// 恢复现场check[i] = false;path.pop_back();}}
};
优美的排列
题目链接

题解
1. 画出决策树
2. 全局变量的设计:ret用来记录优美排列的个数,check数组检查是否可以剪枝,n设计成全局变量就不需要进行传参了
3. 剪枝:第一种剪枝不能出现重复的数,第二种剪枝不满足整除条件的
4. 回溯:如图我们每个位置都要进行判断,每个位置都会走一遍,递归完后进行恢复现场,把最后一位pop_back
5. 递归出口:当path路径的长度等于n时为递归出口
6. for循环的i = 1开始是因为要遍历所有的路径,dfs中pos+1是因为此位置遍历完会来到下一个位置进行遍历,画出决策树就很清晰了

代码
class Solution
{
public:int n;int ret;bool check[16];vector<int> path;int countArrangement(int _n) {n = _n;dfs(1);return ret;}void dfs(int pos){if(path.size() == n){ret++;return;}for(int i = 1;i <= n;i++){if(pos % i == 0 || i % pos == 0){if(check[i] == false){check[i] = true;path.push_back(i);dfs(pos+1);// 恢复现场path.pop_back();check[i] = false;}} }}
};
二、子集问题
字母大小写全排列
题目链接

题解
1. 画出决策树
2. 全局变量:ret记录最终的结果,path记录每次的路径
3. 剪枝:没有剪枝
4. 回溯:到达叶子节点的时候记录完进行回溯,pop_back最后一个位置的数来到上一层
5. 递归出口:pos位置为n时,是最后一个数据的下一个位置,为递归出口
6. 这题和选和不选基本上是一样的,子集问题,pos这个位置变或者不变然后来到下一个位置,所以dfs(pos+1),变的情况为小写字母时转为大写字母,大写转为小写,不变就直接push

代码
class Solution
{
public:vector<string> ret;string path;vector<string> letterCasePermutation(string s) {dfs(s,0);return ret;}void dfs(string s,int pos){// 为什么不能写pos == s.size()if(pos == s.size()){ret.push_back(path);return;}char ch = s[pos];// 不变path.push_back(ch);dfs(s,pos+1);path.pop_back();// 变if(ch < '0' || ch > '9'){ch = change(ch);path.push_back(ch);dfs(s,pos+1);path.pop_back();}}char change(char ch){if(ch >= 'a' && ch <= 'z') ch -= 32;else ch += 32;return ch;}
};
找出所有子集的异或总和再求和
题目链接

题解
1. 画出决策树
2. 全局变量:用sum记录最终的结果,用path记录一个集合的异或和
3. 剪枝:没有剪枝
4. 回溯:每次异或当前元素就抵消掉这个元素了,然后回到上一层
5. 递归出口:没有递归出口,每次把path加到sum中即可
6. for循环中每次从pos位置开始向后枚举,避免重复,dfs(i+1),i+1就是数组中下一个位置的数,相当于剪枝了

代码
class Solution
{
public:long long sum = 0;// 记录全部路径的异或和long long path = 0;// 记录一条路径的异或和int subsetXORSum(vector<int>& nums) {dfs(nums,0);return sum;}void dfs(vector<int> nums,int pos){sum += path;for(int i = pos;i < nums.size();i++){path ^= nums[i];dfs(nums,i+1);path ^= nums[i];// 不能使用pos代替i,如果进行回溯回来,// 进行循环,path始终异或nums[pos]}}
};
三、组合问题
电话号码的字母组合
题目链接

题解
1. 画出决策树
2. 全局变量:ret记录最终的结果,path记录每次的路径,哈希表记录下标的映射关系
3. 剪枝:没有剪枝
4. 回溯:到达叶子节点时,pop_back最后一个元素,恢复现场
5. 递归出口:path的长度和给定的数组的长度相同
6. for循环把所有的数都枚举出来了,所以i下标从0开始,dfs(pos+1),每个位置枚举完跳到下一个位置继续枚举,这题主要是建立一个hash表记录每次的电话号码的数字对应的映射字符串

代码
class Solution
{
public:vector<string> ret;string path;string hash[10] = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};vector<string> letterCombinations(string digits) {if(digits.size() == 0) return ret;dfs(digits,0); return ret;}void dfs(string digits,int pos){if(pos == digits.size()){ret.push_back(path);return;}for(int i = 0;i < hash[digits[pos] - '0'].size();i++){path.push_back(hash[digits[pos] - '0'][i]);dfs(digits,pos+1);// 恢复现场path.pop_back();}}
};
括号生成
题目链接

题解
1. 画出决策树
2. 全局变量:path记录每次的路径,ret记录最终的结果,left记录左括号的数量,right记录右括号的数量
3. 剪枝:第一种右括号的数量大于等于左括号的数量,第二中左括号的数量大于等于n的数量,开始的时候只能给左扩号,右括号剪枝,左右括号相等时,不能加入右括号,左括号大于n不符合结果,等于n时,再往下加就大于n,需要剪枝
4. 回溯:如果是左括号回溯,pop_back最后一个元素,left–,如果是右括号回溯,pop_back最后一个元素,right–
5. 递归出口:右括号的数量等于n时,为左右括号相等的最终结果

代码
class Solution
{
public:vector<string> ret;string path;int left,right;vector<string> generateParenthesis(int n) {dfs(n);return ret;}void dfs(int n){if(right == n){ret.push_back(path);return ;}if(left < n){path.push_back('(');left++;dfs(n);path.pop_back();left--;}if(left > right){path.push_back(')');right++;dfs(n);path.pop_back();right--;}}
};
组合
题目链接

题解
1. 画出决策树
2. 全局变量:path记录每次的路径,ret记录最终的结果,k变为全局变量,不需要多传一个参数
3. 剪枝:不能重复出现相同的数剪枝,长度不够k也剪枝,这题可以从pos位置开始向后枚举,dfs(i+1),i+1表示每次枚举当前数的下一个位置的数,相当于进行了剪枝
4. 回溯:到达叶子节点后,每次pop_back最后一个数
5. 递归出口:path的长度等于k的大小

代码
class Solution
{
public:vector<vector<int>> ret;vector<int> path;int k;vector<vector<int>> combine(int n, int _k) {k = _k;dfs(n,1);return ret;}void dfs(int n,int pos){if(path.size() == k){ret.push_back(path);return ;}for(int i = pos;i <= n;i++){path.push_back(i);dfs(n,i+1);path.pop_back();// 恢复现场}}
};
目标和
题目链接

题解
1. 画出决策树
2. 全局变量:ret记录最终的结果,target变为全局变量就少传一个参数
3. 剪枝:没有剪枝
4. 回溯:函数自动回溯,将path作为参数,记录每条路径的大小,回到上一层,path变为了没有加减之前的path
5. 递归出口:最终的path等于target的大小,ret++
6. 这题就是每个数两种情况,要么加,要么减

代码
class Solution
{
public:int ret,target;int findTargetSumWays(vector<int>& nums, int _target) {target = _target;dfs(nums,0,0);return ret;}void dfs(vector<int>& nums,int pos,int path){if(pos == nums.size()){if(target == path)ret++;return;}// 加法,放在参数中函数帮我们自动恢复现场了dfs(nums,pos+1,path + nums[pos]);// 减法dfs(nums,pos+1, path - nums[pos]);}
};
组合总和
题目链接

题解
解法一:每个pos位置填
1. 画出决策树
2. 全局变量:ret记录最终的结果,path记录每条路径,target变为全局变量就不需要传参target了
3. 剪枝:如果这条路径的和大于target剪枝,如果选择的路径重复了剪枝,这种可以使用i = pos位置开始向后枚举,dfs(i),i表示每次从当前这个数向后枚举,不需要重复枚举这个数前面的数,就避免了重复的路径,达到了剪枝的效果
4. 回溯:sum在函数的参数中自动进行了回溯,pop_back最后一个元素,回到上一层
5. 递归出口:目标值等于记录的路径总和就找到一条路径,加入ret中,并返回给上一层

代码
class Solution
{
public:vector<vector<int>> ret;vector<int> path;int target;vector<vector<int>> combinationSum(vector<int>& candidates, int _target) {target = _target;dfs(candidates,0,0);return ret;}void dfs(vector<int> candidates,int pos,int sum){if(sum == target){ret.push_back(path);return;}// 枚举完了所有的位置也不是target,这个总和比较小if(pos == candidates.size()) return;for(int i = pos;i < candidates.size();i++){if(sum > target) continue;path.push_back(candidates[i]);dfs(candidates,i,sum + candidates[i]);path.pop_back();}}
};
解法二:枚举每个数的数量
1. 和解法一不一样的地方就是dfs函数的实现不一样

class Solution
{
public:vector<vector<int>> ret;vector<int> path;int target;vector<vector<int>> combinationSum(vector<int>& candidates, int _target) {target = _target;dfs(candidates,0,0);return ret;}void dfs(vector<int> candidates,int pos,int sum){if(sum == target){ret.push_back(path);return;}// 枚举完了所有的位置也不是target,这个总和比较小if(pos == candidates.size() || sum > target) return;// 枚举个数for(int k = 0;sum + k * candidates[pos] <= target;k++){if(k) path.push_back(candidates[pos]);dfs(candidates,pos+1,sum + k*candidates[pos]);}// 整块恢复现场for(int k = 1;sum + k * candidates[pos] <= target;k++){path.pop_back();}}
};
总结
1. 最重要的就是画出决策树
2. 全局变量:一般是path记录路径,ret记录各个路径的结果
3. 剪枝:看题目分析和看决策树
4. 回溯:一般是pop_back最后一个元素
5. 递归出口:看题目条件,或者是叶子节点

相关文章:
dfs刷题排列问题 + 子集问题 + 组和问题总结
文章目录 一、排列问题全排列II题解代码 优美的排列题解代码 二、子集问题字母大小写全排列题解代码 找出所有子集的异或总和再求和题解代码 三、组合问题电话号码的字母组合题解代码 括号生成题解代码 组合题解代码 目标和题解代码 组合总和题解代码 总结 一、排列问题 全排列…...
citrix安装部署
在Citrix环境中,特别是在Citrix XenApp或Citrix XenDesktop的部署中,涉及到多个步骤和考虑因素。Citrix是一家提供虚拟化桌面和应用程序解决方案的公司,其产品可以帮助企业实现桌面和应用虚拟化,从而提升灵活性、安全性和管理效率…...
ffmpeg库视频硬编码使用流程
一、硬件编码核心流程 硬件设备初始化 // 创建CUDA硬件设备上下文 AVBufferRef *hw_device_ctx NULL; av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_CUDA, NULL, NULL, 0);// 绑定硬件设备到编码器上下文 codec_ctx->hw_device_ctx av_buffer_…...
996引擎-接口测试:消息Tips
996引擎-接口测试:消息Tips 发送视野内广播消息 sendrefluamsg发送聊天框消息 sendmsg发送地图消息 sendmapmsg打印消息到控制台 release_print发送自定义颜色的文字信息 guildnoticemsg测试NPC参考资料发送视野内广播消息 sendrefluamsg function npc_test_onclick1(player)-…...
【入门初级篇】布局类组件的使用(1)
【入门初级篇】布局类组件的使用(1) 视频要点 (1)章节大纲介绍 (2)布局类组件类型介绍:行布局、列布局、标题 (3)实操演示:列表统计查询布局模型 点击访问my…...
JavaWeb之WebSocket
目录 一、 websocket 概念二、WebSocket原理三、WebSocket特点四、WebSocket应用场景五、Websocket基本使用1、创建Websocket对象2、Websocket事件3、Websocket方法4、前端服务程序 六、聊天室案例1、Tomcat版本:8.0.442、Maven 依赖:3、前端代码4、后端…...
算法2--两数相加
题目描述 解题思路 题目说的很详细了,也就是把每个数倒序写成链表进行输入,然后让你计算两个倒序数组的和,要保证跟预期的结果一样。 首先应该考虑的是两个数组的长度问题,对于链表的每一位进行加法运算,如果两个列表…...
突破边界:Tauri 2.0全局状态管理的原子级实践
精心打造的Tauri 2.0全局状态管理深度指南,融合最新框架特性与企业级实践方案: 一、Tauri 2.0状态管理新范式 1.1 量子态存储模型 #mermaid-svg-paiGRksb0JRQ3TqJ {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fil…...
Springboot的jak安装与配置教程
目录 Windows系统 macOS系统 Linux系统 Windows系统 下载JDK: 访问Oracle官网或其他JDK提供商网站,下载适合Windows系统的JDK版本。网站地址:Oracle 甲骨文中国 | 云应用和云平台点击进入下滑,点击进入下载根据自己的系统选择&…...
Axure大屏可视化模板:赋能多领域,开启数据展示新篇章
在当今这个数据爆炸的时代,数据已经成为各行各业的核心资产。然而,如何高效、直观地展示数据,并将其转化为有价值的决策依据,成为了许多企业和组织面临的共同挑战。Axure大屏可视化模板,作为一款强大的数据展示工具&am…...
大模型训练为什么选择交叉熵损失(Cross-Entropy Loss):均方误差(MSE)和交叉熵损失的深入对比
交叉熵损失:深度学习中的基石与洞见 交叉熵损失(Cross-Entropy Loss)是现代深度学习中分类任务的核心损失函数,尤其在训练大规模模型(如 transformers 等大型语言模型 LLM)时,几乎无处不在。对…...
C++|GLog开源库的使用 如何实现自定义类型消息日志
参考: C glog使用教程与代码演示 C第三方日志库Glog的安装与使用超详解 GLOG从入门到入门 glog 设置日志级别_glog C版本代码分析 文章目录 日志等级自定义消息创建使用宏定义 日志等级 在 glog 中,日志的严重性是通过 LogSeverity 来区分的,…...
cursor常用快捷键(JetBrains Darcula主题风格)
一、基础操作速查 打开/创建项目 打开项目:Ctrl Shift O(选择文件夹)新建文件:Ctrl N保存文件:Ctrl S关闭当前标签页:Ctrl F4 代码编辑 复制当前行:Ctrl D删除当前行:Ctrl …...
区块链学习总结
Hardhat 是一个用于 Ethereum 智能合约开发 的开发环境,专为 Solidity 语言编写的智能合约提供工具支持。它能够帮助开发者 编译、部署、测试和调试 智能合约,并提供一个本地的以太坊测试网络。 Hardhat 的核心功能 本地开发网络(Hardhat Ne…...
《深入剖析鸿蒙生态原生应用:一次开发多端部署的技术革新》
在数字化时代飞速发展的浪潮中,鸿蒙生态以其独特的技术理念和强大的创新能力,为开发者和用户带来了全新的体验。其中,“一次开发多端部署”作为鸿蒙生态原生应用开发的核心技术之一,不仅是技术上的重大突破,更是对未来…...
知识蒸馏:让大模型“瘦身“而不失智慧的魔术
引言:当AI模型需要"减肥" 在人工智能领域,一个有趣的悖论正在上演:大模型的参数规模每年以10倍速度增长,而移动设备的算力却始终受限。GPT-4的1750亿参数需要价值500万美元的GPU集群运行,但现实中的智能设备…...
JavaScript 获取 URL 中参数值的详解
JavaScript 获取 URL 中参数值的详解 1. 了解 URL 参数2. 使用 URLSearchParams 获取参数值2.1 什么是 URLSearchParams?2.2 示例代码2.3 优缺点 3. 使用正则表达式获取参数值3.1 示例代码3.2 分析 4. 自定义解析函数4.1 示例代码4.2 分析 5. 小结与注意事项 在开发…...
识别并脱敏上传到deepseek/chatgpt的文本文件中的身份证/手机号
本文将介绍一种简单高效的方法解决用户在上传文件到DeepSeek、ChatGPT,文心一言,AI等大语言模型平台过程中的身份证号以及手机号等敏感数据识别和脱敏问题。 DeepSeek、ChatGPT,Qwen,Claude等AI平台工具快速的被接受和使用,用户每天上传的文本数据中潜藏着大量敏感信息,…...
ruoyi-vue部署4
1.jdk-linux安装 2.tomcat-linux安装 3.ruoy后台部署 4.nginx-linux安装5.ruoyi前端部署...
【秣厉科技】LabVIEW工具包——OpenCV 教程(12):机器学习
文章目录 前言机器学习例1:支持向量机(SVM)做平面向量二分类例2: K邻近算法(KNearest)实现分类 总结 前言 需要下载安装OpenCV工具包的朋友,请前往 此处 ;系统要求:Wind…...
分布式事务解决方案简介
一、分布式事务的挑战 在分布式系统中,多个服务协同完成一个业务操作时,可能会遇到数据一致性问题。传统单体应用的ACID事务无法直接扩展到分布式环境,主要矛盾在于: • 网络不可靠:服务间通信可能失败。 • 并发冲突…...
【leetcode hot 100 17】电话号码的字母组合
分析:当设计关键字“所有组合”时,要考虑深度优先遍历、广度优先遍历(层次遍历),其中: 深度优先搜索: 自顶向下的递归实现深搜定义子问题在当前递归层结合子问题结果解决原问题 广度优先搜索 利…...
UI数据处理新隐私保护:确保用户新信息安全
hello宝子们...我们是艾斯视觉擅长ui设计和前端数字孪生、大数据、三维建模、三维动画10年经验!希望我的分享能帮助到您!如需帮助可以评论关注私信我们一起探讨!致敬感谢感恩! 在这个数字时代,我们的个人信息似乎无处不在。从社交媒体上的点滴分享,到在线…...
【Javascrip】Javascript练习01 REST API using Express.js.
针对该问题的项目路径 要求部分 what you need to doReview the tasks provided in the section below.Obtain the boilerplate code.Use your local development environment to implement a solution.Upload your solution for marking via Gradescope. There is no attempt…...
分析K8S中Node状态为`NotReady`问题
在Kubernetes(k8s)集群中,Node状态为NotReady通常意味着节点上存在某些问题,下面为你分析正常情况下节点应运行的容器以及解决NotReady状态的方法。 正常情况下Node节点应运行的容器 1. kubelet kubelet是节点上的核心组件&…...
小样本学习综述
小样本学习综述 📕[1]潘雪玲,李国和,郑艺峰. 面向深度网络的小样本学习综述 [J]. 计算机应用研究, 2023, 40 (10): 2881-28882895. DOI:10.19734/j.issn.1001-3695.2023.02.0074. 主要是该论文的一些摘要。 小样本学习旨在利用较少目标数据训练模型快速学习的。 …...
挂谷问题与挂谷猜想:从平面转针到高维拓扑
挂谷问题与挂谷猜想:从平面转针到高维拓扑 目录 挂谷问题的起源数学定义与基本性质研究进展挂谷集合与挂谷猜想王虹与Joshua Zahl的突破意义与影响 挂谷问题的起源 1917年,日本数学家挂谷宗一(かけや そういち Soichi Kakeya,1886-1947)提…...
火语言RPA--表格数据导出
表格数据导出 🚩【组件功能】:导出表格内数据到指定的文件 配置预览 配置说明 导出格式 Excel:导出Excel文档格式,CSV:导出CSV数据格式。 导出文件夹 支持T或# 导出文件需要保存的文件夹路径。 导出文件名支持T或# 导出文…...
数学建模:MATLAB卷积神经网络
一、简述 卷积神经网络是一种处理具有网格结构数据的深度学习模型,由输入层、卷积层、池化层、全连接层、输出层组成。 输出层:将图像转换为其对应的由像素值构成的二维矩阵,并存储二维矩阵 卷积层:提取图像的底层特征…...
Vue3 基础语法指南:响应式系统与 Ref 应用
1、Reactive 的深度响应式 1.1、基本用法 vue <script setup> import { reactive } from vueconst state reactive({count: 0,user: {name: Alice,age: 30} })const increment () > state.count const updateName () > state.user.name Bob </script>1…...
