当前位置:   article > 正文

Day30 78子集 90子集II 491非递减子序列

Day30 78子集 90子集II 491非递减子序列

78 子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

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

        注意本题与之前所有回溯算法所不同的一点就是,之前都是在叶子节点返回结果,而求子集需要在所有节点返回结果。同时这个终止条件可写可不写。

  1. class Solution {
  2. private:
  3. vector<vector<int>> result;
  4. vector<int> path;
  5. void backtracking(vector<int>& nums, int startIndex) {
  6. result.push_back(path);
  7. if(startIndex == nums.size()) {
  8. return;
  9. }
  10. for(int i = startIndex; i < nums.size(); i++) {
  11. path.push_back(nums[i]);
  12. backtracking(nums, i+1);
  13. path.pop_back();
  14. }
  15. }
  16. public:
  17. vector<vector<int>> subsets(vector<int>& nums) {
  18. result.clear();
  19. path.clear();
  20. backtracking(nums,0);
  21. return result;
  22. }
  23. };

 90 子集II

 

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

  • 输入: [1,2,2]
  • 输出: [ [2], [1], [1,2,2], [2,2], [1,2], [] ]

 

        本题是上一题和组合总和II的结合题,需要注意以下几点:终止条件可以不写,最后因为for循环一定都会终止;每次递归都返回结果,并不仅仅是叶子节点;注意树层去重和树枝去重的区别,本题要求树层去重才会直接continue;在回溯之前要现将nums排序才行。

  1. class Solution {
  2. private:
  3. vector<vector<int>> result;
  4. vector<int> path;
  5. void backtracking(vector<int>& nums, int startIndex, vector<bool>& used) {
  6. result.push_back(path);
  7. for (int i = startIndex; i < nums.size(); i++) {
  8. // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
  9. // used[i - 1] == false,说明同一树层candidates[i - 1]使用过
  10. // 而我们要对同一树层使用过的元素进行跳过
  11. if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
  12. continue;
  13. }
  14. path.push_back(nums[i]);
  15. used[i] = true;
  16. backtracking(nums, i + 1, used);
  17. used[i] = false;
  18. path.pop_back();
  19. }
  20. }
  21. public:
  22. vector<vector<int>> subsetsWithDup(vector<int>& nums) {
  23. result.clear();
  24. path.clear();
  25. vector<bool> used(nums.size(), false);
  26. sort(nums.begin(), nums.end()); // 去重需要排序
  27. backtracking(nums, 0, used);
  28. return result;
  29. }
  30. };

         set去重版本:同样也是树层去重,因为如果经历过递归以后不算重复,只有for循环的时候才算重复(树层去重)

  1. class Solution {
  2. private:
  3. vector<vector<int>> result;
  4. vector<int> path;
  5. void backtracking(vector<int>& nums, int startIndex) {
  6. result.push_back(path);
  7. unordered_set<int> uset; //去重集合
  8. for (int i = startIndex; i < nums.size(); i++) {
  9. if (uset.find(nums[i]) != uset.end()) { //如果集合里面有了这个元素,说明重复
  10. continue;
  11. }
  12. uset.insert(nums[i]); //每次插入一个进入集合里面
  13. path.push_back(nums[i]);
  14. backtracking(nums, i + 1);
  15. path.pop_back();
  16. }
  17. }
  18. public:
  19. vector<vector<int>> subsetsWithDup(vector<int>& nums) {
  20. result.clear();
  21. path.clear();
  22. sort(nums.begin(), nums.end()); // 去重需要排序
  23. backtracking(nums, 0);
  24. return result;
  25. }
  26. };

 startIndex:

  1. class Solution {
  2. private:
  3. vector<vector<int>> result;
  4. vector<int> path;
  5. void backtracking(vector<int>& nums, int startIndex) {
  6. result.push_back(path);
  7. for (int i = startIndex; i < nums.size(); i++) {
  8. // 而我们要对同一树层使用过的元素进行跳过
  9. if (i > startIndex && nums[i] == nums[i - 1] ) { // 注意这里使用i > startIndex
  10. continue;
  11. }
  12. path.push_back(nums[i]);
  13. backtracking(nums, i + 1);
  14. path.pop_back();
  15. }
  16. }
  17. public:
  18. vector<vector<int>> subsetsWithDup(vector<int>& nums) {
  19. result.clear();
  20. path.clear();
  21. sort(nums.begin(), nums.end()); // 去重需要排序
  22. backtracking(nums, 0);
  23. return result;
  24. }
  25. };

 491 非递减子序列

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。

示例:

  • 输入: [4, 6, 7, 7]
  • 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]

说明:

  • 给定数组的长度不会超过15。
  • 数组中的整数范围是 [-100,100]。
  • 给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。

        本题同样需要去重,同时加了递增,并且不能排序,所以不能使用used数组的方法了,于是用set来进行去重:注意每层里面都有一个set维护,他只负责自己这一层的。

  1. class Solution {
  2. private:
  3. vector<vector<int>> result;
  4. vector<int> path;
  5. void backtracking(vector<int>& nums, int startIndex) {
  6. if (path.size() > 1) {
  7. result.push_back(path);
  8. // 注意这里不要加return,要取树上的节点
  9. }
  10. unordered_set<int> uset; // 使用set对本层元素进行去重
  11. for (int i = startIndex; i < nums.size(); i++) {
  12. if ((!path.empty() && nums[i] < path.back())
  13. || uset.find(nums[i]) != uset.end()) {
  14. continue;
  15. }
  16. uset.insert(nums[i]); // 记录这个元素在本层用过了,本层后面不能再用了
  17. path.push_back(nums[i]);
  18. backtracking(nums, i + 1);
  19. path.pop_back();
  20. }
  21. }
  22. public:
  23. vector<vector<int>> findSubsequences(vector<int>& nums) {
  24. result.clear();
  25. path.clear();
  26. backtracking(nums, 0);
  27. return result;
  28. }
  29. };

以上代码用我用了unordered_set<int>来记录本层元素是否重复使用。

其实用数组来做哈希,效率就高了很多

注意题目中说了,数值范围[-100,100],所以完全可以用数组来做哈希。

程序运行的时候对unordered_set 频繁的insert,unordered_set需要做哈希映射(也就是把key通过hash function映射为唯一的哈希值)相对费时间,而且每次重新定义set,insert的时候其底层的符号表也要做相应的扩充,也是费事的。

那么优化后的代码如下:nums[i] + 100保证方括号里的值永远大于等于0

  1. class Solution {
  2. private:
  3. vector<vector<int>> result;
  4. vector<int> path;
  5. void backtracking(vector<int>& nums, int startIndex) {
  6. if (path.size() > 1) {
  7. result.push_back(path);
  8. }
  9. int used[201] = {0}; // 这里使用数组来进行去重操作,题目说数值范围[-100, 100]
  10. for (int i = startIndex; i < nums.size(); i++) {
  11. if ((!path.empty() && nums[i] < path.back())
  12. || used[nums[i] + 100] == 1) {
  13. continue;
  14. }
  15. used[nums[i] + 100] = 1; // 记录这个元素在本层用过了,本层后面不能再用了
  16. path.push_back(nums[i]);
  17. backtracking(nums, i + 1);
  18. path.pop_back();
  19. }
  20. }
  21. public:
  22. vector<vector<int>> findSubsequences(vector<int>& nums) {
  23. result.clear();
  24. path.clear();
  25. backtracking(nums, 0);
  26. return result;
  27. }
  28. };
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/39740
推荐阅读
相关标签
  

闽ICP备14008679号