本周小结!(回溯算法系列三)续集

在 本周小结!(回溯算法系列三) 中一位录友对 整颗树的本层和同一节点的本层有疑问,也让我重新思考了一下,发现这里确实有问题,所以专门写一篇来纠正,感谢录友们的积极交流哈!

接下来我再把这块再讲一下。

在回溯算法:求子集问题(二)中的去重和 回溯算法:递增子序列中的去重 都是 同一父节点下本层的去重。

回溯算法:求子集问题(二)也可以使用set针对同一父节点本层去重,但子集问题一定要排序,为什么呢?

我用没有排序的集合{2,1,2,2}来举例子画一个图,如图:

图中,大家就很明显的看到,子集重复了。

那么下面我针对回溯算法:求子集问题(二) 给出使用set来对本层去重的代码实现。

90.子集II

used数组去重版本: 回溯算法:求子集问题(二)

使用set去重的版本如下:

class Solution {
private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& nums, int startIndex, vector<bool>& used) {result.push_back(path);unordered_set<int> uset; // 定义set对同一节点下的本层去重for (int i = startIndex; i < nums.size(); i++) {if (uset.find(nums[i]) != uset.end()) { // 如果发现出现过就passcontinue;}uset.insert(nums[i]); // set跟新元素path.push_back(nums[i]);backtracking(nums, i + 1, used);path.pop_back();}}public:vector<vector<int>> subsetsWithDup(vector<int>& nums) {result.clear();path.clear();vector<bool> used(nums.size(), false);sort(nums.begin(), nums.end()); // 去重需要排序backtracking(nums, 0, used);return result;}
};

针对留言区录友们的疑问,我再补充一些常见的错误写法,

错误写法一

把uset定义放到类成员位置,然后模拟回溯的样子 insert一次,erase一次。

例如:

class Solution {
private:vector<vector<int>> result;vector<int> path;unordered_set<int> uset; // 把uset定义放到类成员位置void backtracking(vector<int>& nums, int startIndex, vector<bool>& used) {result.push_back(path);for (int i = startIndex; i < nums.size(); i++) {if (uset.find(nums[i]) != uset.end()) {continue;}uset.insert(nums[i]);   // 递归之前insert path.push_back(nums[i]);backtracking(nums, i + 1, used);path.pop_back();uset.erase(nums[i]);    // 回溯再erase}}

在树形结构中,如果把unordered_set uset放在类成员的位置(相当于全局变量),就把树枝的情况都记录了,不是单纯的控制某一节点下的同一层了

如图:

可以看出一旦把unordered_set uset放在类成员位置,它控制的就是整棵树,包括树枝。

所以这么写不行!

错误写法二

有同学把 unordered_set uset; 放到类成员位置,然后每次进入单层的时候用uset.clear()。

代码如下:

class Solution {
private:vector<vector<int>> result;vector<int> path;unordered_set<int> uset; // 把uset定义放到类成员位置void backtracking(vector<int>& nums, int startIndex, vector<bool>& used) {result.push_back(path);uset.clear(); // 到每一层的时候,清空usetfor (int i = startIndex; i < nums.size(); i++) {if (uset.find(nums[i]) != uset.end()) {continue;}uset.insert(nums[i]); // set记录元素path.push_back(nums[i]);backtracking(nums, i + 1, used);path.pop_back();}}

uset已经是全局变量,本层的uset记录了一个元素,然后进入下一层之后这个uset(和上一层是同一个uset)就被清空了,也就是说,层与层之间的uset是同一个,那么就会相互影响。

所以这么写依然不行!

组合问题和排列问题,其实也可以使用set来对同一节点下本层去重,下面我都分别给出实现代码

40. 组合总和 II

使用used数组去重版本:回溯算法:求组合总和(三)

使用set去重的版本如下:

class Solution {
private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {if (sum == target) {result.push_back(path);return;}unordered_set<int> uset; // 控制某一节点下的同一层元素不能重复for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {if (uset.find(candidates[i]) != uset.end()) {continue;}uset.insert(candidates[i]); // 记录元素sum += candidates[i];path.push_back(candidates[i]);backtracking(candidates, target, sum, i + 1); sum -= candidates[i];path.pop_back();}}public:vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {path.clear();result.clear();sort(candidates.begin(), candidates.end());backtracking(candidates, target, 0, 0);return result;}
};

47. 全排列 II

使用used数组去重版本:回溯算法:排列问题(二)

使用set去重的版本如下:

class Solution {
private:vector<vector<int>> result;vector<int> path;void backtracking (vector<int>& nums, vector<bool>& used) {if (path.size() == nums.size()) {result.push_back(path);return;}unordered_set<int> uset; // 控制某一节点下的同一层元素不能重复for (int i = 0; i < nums.size(); i++) {if (uset.find(nums[i]) != uset.end()) {continue;}if (used[i] == false) {uset.insert(nums[i]); // 记录元素  used[i] = true;path.push_back(nums[i]);backtracking(nums, used);path.pop_back();used[i] = false;}}}
public:vector<vector<int>> permuteUnique(vector<int>& nums) {result.clear();path.clear();sort(nums.begin(), nums.end()); // 排序vector<bool> used(nums.size(), false);backtracking(nums, used);return result;}
};

两种写法的性能分析

需要注意的是:使用set去重的版本相对于used数组的版本效率都要低很多,大家在leetcode上提交,能明显发现。

原因在回溯算法:递增子序列中也分析过,主要是因为程序运行的时候对unordered_set 频繁的insert,unordered_set需要做哈希映射(也就是把key通过hash function映射为唯一的哈希值)相对费时间,而且insert的时候其底层的符号表也要做相应的扩充,也是费时的。

而使用used数组在时间复杂度上几乎没有额外负担!

使用set去重,不仅时间复杂度高了,空间复杂度也高了,在本周小结!(回溯算法系列三)中分析过,组合,子集,排列问题的空间复杂度都是O(n),但如果使用set去重,空间复杂度就变成了O(n^2),因为每一层递归都有一个set集合,系统栈空间是n,每一个空间都有set集合。

那有同学可能疑惑 用used数组也是占用O(n)的空间啊?

used数组可是全局变量,每层与每层之间公用一个used数组,所以空间复杂度是O(n + n),最终空间复杂度还是O(n)。

总结

本篇本打算是对本周小结!(回溯算法系列三)的一个点做一下纠正,没想到又写出来这么多!

这个点都源于一位录友的疑问,然后我思考总结了一下,就写着这一篇,所以还是得多交流啊!

如果大家对「代码随想录」文章有什么疑问,尽管打卡留言的时候提出来哈,或者在交流群里提问。

其实这就是相互学习的过程,交流一波之后都对题目理解的更深刻了,我如果发现文中有问题,都会在评论区或者下一篇文章中即时修正,保证不会给大家带跑偏!

就酱,「代码随想录」一直都是干货满满,公众号里的一抹清流,值得推荐给身边的每一位同学朋友!

我是程序员Carl,可以找我组队刷题,也可以在B站上找到我,本文leetcode刷题攻略已收录,更多精彩算法文章尽在公众号:代码随想录,关注后就会发现和「代码随想录」相见恨晚!

如果感觉对你有帮助,不要吝啬给一个

总结leetcode上【排列问题】【组合问题】【子集问题】回溯算法去重的两种写法!相关推荐

  1. sum 去重_总结leetcode上【排列问题】【组合问题】【子集问题】回溯算法去重的两种写法!...

    本周小结!(回溯算法系列三)续集 在 本周小结!(回溯算法系列三) 中一位录友对 整颗树的本层和同一节点的本层有疑问,也让我重新思考了一下,发现这里确实有问题,所以专门写一篇来纠正,感谢录友们的积极交 ...

  2. 【C++代码】求全排列、组合、子集

    在力扣上刷到的这类题,做一个总结,以下例子均来源于力扣.思路相似,都可以用回溯(决策树)解决. 这类题型的原理可参考这两篇题解,讲得非常详细! 1.扒一扒回溯算法的裤子 2.回溯思想团灭排列.组合.子 ...

  3. java数组求子集_回溯算法:求子集问题!

    给「代码随想录」一个星标吧! ❝ 认识本质之后,这就是一道模板题 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集). 说明:解集不能包含重复的子集. 示例: 输入: nums ...

  4. 算法题:一个圆环上有100个灯泡,灯泡有打开和关闭两种状态,灯泡的状态随机,按一个灯泡的开关,相邻的两个灯泡的状态也发生一次变化。比如暗-亮-暗,按中间灯泡,变化为亮-暗-亮。问设计一道算法,使得所有

    算法题:一个圆环上有100个灯泡,灯泡有打开和关闭两种状态,灯泡的状态随机,按一个灯泡的开关,相邻的两个灯泡的状态也发生一次变化.比如暗-亮-暗,按中间灯泡,变化为亮-暗-亮.问设计一道算法,使得所有 ...

  5. 生成排列(全排列)的两种写法

    问题简述:输出任意各自然数(可不连续)所有不重复的排列,即全排列,要求所产生的任一数字序列中不允许出现重复的数字. 解决方法:1.交换法    2.数组访问法,两种方法都是由dfs回溯完成,也可以使用 ...

  6. eclipse让实现类也添加上接口的注释_多线程:面试常问的两种创建方式,数据共享实现和正确停止线程...

    多线程 进程与线程的区别: 进程:程序的执行过程,持有资源(内存)(共享内存和文件)和线程.比如,电脑上的eclipse.QQ.微信等运行中的软件就是一个进程 线程应用:1.eclipse编辑代码时, ...

  7. [Leetcode][第81题][JAVA][N皇后问题][回溯算法]

    [问题描述][困难] [解答思路] 1. 主副对角线列 标记 复杂度 import java.util.ArrayDeque; import java.util.ArrayList; import j ...

  8. 中文顿号怎么输入_键盘上顿号怎么打出来 顿号用键盘打出来的两种方法

    顿号是我们标点符号中经常会使用的一种,在纸上书写的时候,大家只需要写一个一个斜划线即可,可是在电脑上输入,很多人往往不知道如何输入顿号.那么顿号在键盘上怎么打?顿号怎么打出来?下面脚本之家小编就为大家 ...

  9. 苹果显示4g却上不了网_电脑突然上不了网,自己动手排查,快速定位两种常见问题...

    电脑突然上不了网,估计每个用过windows 的人都遇到过,但是解决问题的办法没有统一的方案,不过只要先确定问题的原因,就能解决一半的问题. 第一步:查看网络状态.连不上网的原因有多种,我们一种一种分 ...

  10. LeetCode上删除链表末尾第N个节点算法——Remove Nth Node From End of List

    1.题目 Given a linked list, remove the n-th node from the end of list and return its head. Example: Gi ...

最新文章

  1. 韩顺平老师 Mysql优化 笔记
  2. PHP与SQL数据库交互中文乱码怎么办
  3. 01.C++(一)----面向对象的思想
  4. javascript学习系列(9):原数组发生变化的情况
  5. eslint常规语法检
  6. Hive的UDF是什么?
  7. hbase 页面访问_HBase在滴滴出行的应用场景和最佳实践
  8. 期货市场技术分析05_交易量和持仓兴趣
  9. django文件——django + jquery-file-upload上传篇(二)-- 插件实现文件上传+进度条显示 +拖入文件上传...
  10. 60度斜坡怎么计算_坡度计算公式图解
  11. 修复Lvgl的roller控件点击位置向上偏移的问题
  12. Android Studio中AndroidManifest.xml文件中application标签
  13. oracle character set mismatch,EntityFramework查询oracle数据库时报ora-12704: character set mismatch...
  14. python34 pandas_python 3 科学计算之pandas入门(一)
  15. 汇编中esp和ebp在函数栈空间的保存和变化 call的参数和局部变量的关系详解
  16. flv.js播放视频时遇到的问题
  17. 应用商店调研-360手机助手
  18. MATLAB学习(一)——APP的学习笔记
  19. spoon无法初始化至少一个步骤_通俗易懂:8大步骤图解注意力机制
  20. 企业申请e-mark认证要怎么做?

热门文章

  1. C++使用major做变量名的问题
  2. [python]设计模式
  3. C# 生成高清缩略图
  4. Android核心分析 之二方法论探讨之概念空间篇
  5. android应用开发全程实录-你有多熟悉listview? getView重写 inflate使用
  6. 【炼数成金 RapidMiner 三 】关联分析、关联规则
  7. golang range循环内部
  8. MySQL灾备恢复在线主从复制变成主主复制及多源复制【转】
  9. php笔记--php安装
  10. .net站点配置完后常见报错及解决措施