回溯问题中组合、子集和排列问题的去重
回溯法,⼀般可以解决如下⼏种问题:
- 组合问题:N个数⾥⾯按⼀定规则找出k个数的集合
- 切割问题:⼀个字符串按⼀定规则有⼏种切割⽅式
- ⼦集问题:⼀个N个数的集合⾥有多少符合条件的⼦集
- 排列问题:N个数按⼀定规则全排列,有⼏种排列⽅式
- 棋盘问题:N皇后,解数独等等
本文主要讲解这几种问题在去重问题上的不同做法,其实就一个核心点,<font color=red>对有序数组进行去重,去重方式包括传递used引用去重和每层定义set去重</font>。
需知:组合问题、切割问题和子集问题无序,排列问题有序
-
对于组合问题,因为组合无序,所以需要排序+used/set去重,组合问题其实是子集问题的一种;
-
对于切割问题,本质就是组合问题,使用startIdx当作切割下标即可;
-
对于子集问题,包括两种题型:
- 子集II:和组合问题去重一样,排序+used/set去重
- 递增子序列:等价于从有序数组中选取所有子集,相当于有序数组的前提已经实现,所以需要使用set进行去重,不能使用used是因为不能进行candidates[i] == candidates[i-1] && used[i-1]==false的判断
-
对于排列问题,说下解题思路和去重问题:
-
解题思路:
- 每层递归都是从0开始搜索;
- 使用used数组记录path里面放了哪些元素
-
去重问题:
- 不用排序,每层定义set去重(只有这一种情况特殊不用排序)
int repeatArr[21] = {0}; for (int i = 0; i < nums.size(); i++) { if (repeatArr[nums[i] + 10] == 1 || used[i]) { // 在当前节点之前出现过该数值的节点 continue; } repeatArr[nums[i] + 10] = 1; }
- 和组合问题一样,先排序,在传递used引用去重
void backtracking(vector<int> &nums, vector<bool> &used) { for (int i = 0; i < nums.size(); i++) { // 如果同⼀树层nums[i - 1]使⽤过则直接跳过 if ((i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) || used[i]){ continue; } } }
- 扩展(树枝去重)
对于排列问题,树层上去重和树枝上去重,都是可以的,但是树层上去重效率更⾼! 树枝去重即: if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true || used[i]) { continue; }
-
-
求和问题中,有序数组的剪枝问题::for (int i = idx; i <= 9 - (k - path.size()) + 1 && sum+i<=n; i++){} //因为集合有序,当sum+i>n时,i之后的数sum+idx必然也大于n,因此加sum+i<=n在for循环判断条件处
思路:去重,即使用过的元素不能重复选取。**<font color=red>组合和子集问题中,有序数组才能做树层去重。</font>**去重有两种思路:
1、**传递used引用只影响startIdx下标为头结点的整棵树;**当遍历到下标i时,如果candidates[i] == candidates[i-1] && used[i-1]==false,说明i-1是i节点在该树层的前节点,如果candidates[i] == candidates[i-1] && used[i-1] == true,说明i-1是i节点的父节点
注意:引用传递和全局变量在回溯中的使用方法一模一样,区别:如果需要使用题目给的nums数组的大小信息进行初始化的话,就在入口函数对used数组进行初始化,并传递引用;如果used不需要进行初始化,直接使用全局变量即可
// ⾸先把给candidates排序,让其相同的元素都挨在⼀起。 sort(candidates.begin(), candidates.end()); void backtracking(vector<int> &candidates, int target, int sum, int startIndex, vector<bool> &used) { for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) { // used[i - 1] == true,说明同⼀树⽀candidates[i - 1]使⽤过 // used[i - 1] == false,说明同⼀树层candidates[i - 1]使⽤过 // 要对同⼀树层使⽤过的元素进⾏跳过 if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) { continue; } used[i] = true; backtracking(candidates, target, sum, i + 1, used); used[i] = false; } }
2、**每层定义set只影响startIdx下标开始的当前树层的节点。**如果set[nums[i]]=true,说明该节点之前已经存在数值为nums[i]的节点
void backtracking(vector<int> &candidates, int target, int sum, int startIndex) { unordered_set<int> uset; // 使⽤set对本层元素进⾏去重 for (int i = startIndex; i < candidates.size(); i++) { // 要对同⼀树层使⽤过的元素进⾏跳过 if (uset.find(candidates[i]) != uset.end() || sum + candidates[i] > target) { continue; } uset.insert(candidates[i]); backtracking(candidates, target, sum, i + 1); // 每层都拥有自己的used数组,不用移除元素 } }
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
线上环境大规模RocketMQ集群不停机优雅升级实践(架构方案)
接安全部门的行政要求,生产环境上百台RocketMQ机器必须在半个月内升级,必须支持ACL,规避安全风险。 RocketMQ集群的升级方案、落地实施就自然而然的落到了我的头上,本文不仅要介绍一下笔者是如何升级的,更想展示作为一名架构师,处理这些问题的方法论,展示大厂架构师的工作日常。 >温馨提示:关于ACL相关的内容,后续文章会单独分享从4.1.0版本升级到4.8并开启ACL的曲折经历。 1、版本升级的迫切性 说来惭愧,作为RocketMQ社区优秀布道师,笔者所在公司的RocketMQ服务端版本竟然还是4.1.0,RocketMQ在4.4.0版本之前是不支持ACL(访问控制),对应生产环境中任意一台机器都可以订阅任意topic,在任意一台生产应用服务器都可以安装一个rocketmq-console,从而控制整个集群,拥有删除主题、删除消费组的权限,想想是不是后背发凉. 2、升级方案 2.1 确定升级到的版本 翻开RocketMQ升级日志,RocketMQ在4.4.0版本正式引入了ACL机制,故版本至少要升级到4.4.0,在业界使用开源版本有一个不成文的规则:通常不要使用最新的版本...
- 下一篇
这一次,彻底搞懂 Go Cond
hi,大家好,我是 haohongfan。 本篇文章会从源码角度去深入剖析下 sync.Cond。Go 日常开发中 sync.Cond 可能是我们用的较少的控制并发的手段,因为大部分场景下都被 Channel 代替了。还有就是 sync.Cond 使用确实也蛮复杂的。 比如下面这段代码: packagemainimport("fmt""time")funcmain(){done:=make(chanint,1)gofunc(){time.Sleep(5*time.Second)done<-1}()fmt.Println("waiting")<-donefmt.Println("done")} 同样可以使用 sync.Cond 来实现 packagemainimport("fmt""sync""time")funcmain(){cond:=sync.NewCond(&sync.Mutex{})varflagboolgofunc(){time.Sleep(time.Second*5)cond.L.Lock()flag=truecond.Signal()cond.L.Un...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- CentOS关闭SELinux安全模块
- CentOS7设置SWAP分区,小内存服务器的救世主
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Mario游戏-低调大师作品