• 含重复元素取不重复子集[如何取子集?如何去重?]


    前言

    发现关键问题是一个人的核心能力,就比如含重复元素取不重复子集,两个关键问题就是,第一,如何取子集?第二,如何去重?
    针对如何取子集,该文整理了三种方式;
    针对如何去重,该文整理了两种方式。

    一、子集II

    在这里插入图片描述

    二、取子集+去重

    package everyday.bitManipulation;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    public class SubsetsWithDup {
        /*
        target:不重复的子集。
        如何取子集?不断新增一个元素。
        如何去重?排序+pre+记录最后被添加的一组的位置。
         */
        public List<List<Integer>> subsetsWithDup(int[] nums) {
            Arrays.sort(nums);
            
            List<List<Integer>> subsets = new ArrayList<>();
            subsets.add(new ArrayList<>());
            
            int pre = 1 << 31, begin = 0;
            for (int num : nums) {
                int i = 0, size = subsets.size();
                
                if (pre == num) i = begin;
    
                while (i < size) {
                    List<Integer> el = new ArrayList<>(subsets.get(i++));
                    el.add(num);
                    subsets.add(el);
                }
                begin = size;
                pre = num;
            }
            return subsets;
        }
    
        /*
        上面我们已经挖掘了两个核心问题,而且采用自己的理解方式完成了题解。
        看一哈官方是怎么搞的高级做法,即官方是如果解决如下两个问题的,
        1-如何取子集?二进制枚举法 + 取该位为1的集合元素,时间复杂度O(2^n^ * n)
        2-如何去重?排序 + 前后两者相等 + 前一位在子集中
         */
        public List<List<Integer>> subsetsWithDup2(int[] nums) {
            int n = nums.length;
    
            Arrays.sort(nums);
    
            List<List<Integer>> subsets = new ArrayList<>();
            List<Integer> subset = new ArrayList<>();
    
            for (int mask = 0; mask < 1 << n; mask++) {
                // 通过二进制的每一位来取元素。
                boolean isValid = true;
                for (int i = 0; i < n; i++) {
                    // 判定该元素是否在当前子集中。
                    if ((mask & 1 << i) != 0) {
                        if (i > 0 && nums[i] == nums[i - 1] && (mask >> i - 1 & 1) == 0) {
                            // 出现重复
                            isValid = false;
                            break;
                        }
                        // 没有重复
                        subset.add(nums[i]);
                    }
                }
                // 重复则不要该重复且半截截子集,不重复就要。
                if (isValid) subsets.add(new ArrayList<>(subset));
                // 清空子集
                subset.clear();
            }
            return subsets;
        }
    
        /*
        上面我们已经挖掘了两个核心问题,而且采用自己的理解方式完成了题解。
        看一哈官方是怎么搞的高级做法,即官方是如果解决如下两个问题的,
        1-如何取子集?选择 or 不选择当前元素。
        2-如何去重?排序+上一个和当前选择的元素没有同时出现 -> 越过此子集。
         */
        public List<List<Integer>> subsetsWithDup3(int[] nums) {
            int n = nums.length;
    
            Arrays.sort(nums);
    
            List<List<Integer>> subsets = new ArrayList<>();
            List<Integer> subset = new ArrayList<>();
    
            dfs(nums, 0, false, subset, subsets);
    
            return subsets;
        }
    
        // 选 or 不选,共考虑n个元素,所以是(1 + 1)^n^
        private void dfs(int[] nums, int cur, boolean isChoosePre, List<Integer> subset, List<List<Integer>> subsets) {
            if (cur == nums.length) {
                subsets.add(new ArrayList<>(subset));
                return;
            }
            // 选择 or 不选择当前元素。
            // 不选择当前元素,屁事没有
            dfs(nums, cur + 1, false, subset, subsets);
            // 选择当前元素,当cur - 1却没选择,且两者相等,就必会出现重复子集,越过。
            if (cur > 0 && nums[cur] == nums[cur - 1] && !isChoosePre) return;
            // 前面的选了 or 前后不相等,正常添加元素,生成子集。
            // 标准回溯
            subset.add(nums[cur]);
            dfs(nums, cur + 1, true, subset, subsets);
            subset.remove(subset.size() - 1);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110

    总结

    1)锻炼自己发现核心问题/提出关键问题的能力。
    2)子集的三种生成方式,循环加元素 | 二进制枚举 | dfs选或不选元素
    3)子集重复的两种判定方式,排序+前后是否相等 | 排序+前后是否相等+前面是否被选

    参考文献

    [1] LeetCode 子集II

  • 相关阅读:
    JS sort排序
    lintcode 367 · 表达树构造 【hard 栈,树】
    xss靶场练习之xss.haozi.me解析及答案
    Spring源码:Bean生命周期(三)
    36.AC自动机:如何用多模式串匹配实现敏感词过滤功能
    艾美捷RPMI-1640培养基L-谷氨酰胺化学性质说明
    Linux项目:自主web服务器
    vue3开发快速入门
    hadoop 常用端口号,常用配置文件都有哪些?hadoop3.x端口号 hadoop(十二)
    封装自己专属的真正的纯净版Windows系统过程记录(2)——使用习惯设置,软件安装与优化设置
  • 原文地址:https://blog.csdn.net/qq_43164662/article/details/125624234