力扣90——子集 II

原题

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

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

示例:

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

原题url:https://leetcode-cn.com/problems/subsets-ii/

解题

递归

这道题,针对已经刷了不少题目的我们而言,应该第一想到的就是递归了,从第1个数开始,每次遍历1个数,如果和之前的数相同则跳过,然后以下一个数为起点,继续遍历。让我们来看看代码:

class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        // 从小到大排序
        Arrays.sort(nums);
        // 最终结果
        List<List<Integer>> result = new LinkedList<>();
        result.add(new LinkedList<>());
        // 回溯
        dfs(0, nums, new Stack<>(), result);

        return result;
    }

    public void dfs(int index, int[] nums, Stack<Integer> stack, List<List<Integer>> result) {
        if (index >= nums.length) {
            return;
        }

        for (int i = index; i < nums.length; i++) {
            // 在这一次总的查找中,如果当前节点和上一个节点相同,则跳过
            if (i > index && nums[i] == nums[i - 1]) {
                continue;
            }
            // 添加该数
            stack.push(nums[i]);
            // 作为一种情况,放进结果中
            result.add(new LinkedList<>(stack));
            // 继续回溯
            dfs(i + 1, nums, stack, result);
            // 回退
            stack.pop();
        }
    }
}

提交OK,执行用时:2 ms,内存消耗:36.5 MB,但执行用时只战胜40.16%,那就来优化一下。

优化

看了第一眼,我真的不知道该如何优化。我先是想到将递归改成迭代,但感觉并没有从时间上做出优化,不过还是给大家看一下:

class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        if (nums == null || nums.length == 0) {
            return new LinkedList<>();
        }

        // 从小到大排序
        Arrays.sort(nums);
        // 最终结果
        List<List<Integer>> result = new ArrayList<>(1 << nums.length);
        result.add(0, new LinkedList<>());
        // 上一步新解的开始下标
        int newStartIndex = 1;
        // 遍历添加
        for (int i = 0; i < nums.length; i++) {

            int j = 0;
            // 和上一个数字相同,则只针对上一步的新解增加
            if (i > 0 && nums[i] == nums[i - 1]) {
                j = newStartIndex;
            }
            int length = result.size();
            newStartIndex = length;
            for (;j < length; j++) {
                List<Integer> tempList = result.get(j);
                List<Integer> newList = new LinkedList<>(tempList);
                newList.add(nums[i]);
                result.add(newList);
            }
        }

        return result;
    }
}

提交之后,果然不出所料,和之前一样,那就再让我们想想。

还记得在之前文章中曾经说过,new LinkedList<>(Collection<? extends E> c)其内部依旧是遍历,很耗性能。因此我专门看了一下new ArrayList<>(Collection<? extends E> c),其内部最终会调用Systemp.arraycopy。让我们再试一次:

class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        // 从小到大排序
        Arrays.sort(nums);
        // 最终结果
        List<List<Integer>> result = new LinkedList<>();
        result.add(new ArrayList<>());
        // 回溯
        dfs(0, nums, new Stack<>(), result);

        return result;
    }

    public void dfs(int index, int[] nums, Stack<Integer> stack, List<List<Integer>> result) {
        if (index >= nums.length) {
            return;
        }

        for (int i = index; i < nums.length; i++) {
            // 在这一次总的查找中,如果当前节点和上一个节点相同,则跳过
            if (i > index && nums[i] == nums[i - 1]) {
                continue;
            }
            // 添加该数
            stack.push(nums[i]);
            // 作为一种情况,放进结果中
            result.add(new ArrayList<>(stack));
            // 继续回溯
            dfs(i + 1, nums, stack, result);
            // 回退
            stack.pop();
        }
    }
}

提交之后,果然OK了,执行用时:1 ms,战胜100%的 java 提交记录。

我这里再说明一下,LinkedList 的遍历拷贝,每个元素都需要重新计算内存位置,而 ArrayList 的拷贝,可以直接一次性申请一大片空间,写入和遍历的速度会更快。

总结

以上就是这道题目我的解答过程了,不知道大家是否理解了。这道题目只要利用递归就可以解决了,但优化的时候,需要注意数据结构(是不是我之前用一些的 LinkedList 换成 ArrayList 会效果更好呢)。

有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。

https://death00.github.io/

公众号:健程之道

原文地址:https://www.cnblogs.com/death00/p/12122973.html

时间: 2024-10-08 16:06:21

力扣90——子集 II的相关文章

[leetcode] 90. 子集 II.md

90. 子集 II 78. 子集题的扩展,其中的元素可能会出现重复了 我们仍沿用78题的代码,稍作改动即可: 此时需要对nums先排个序,方便我们后面跳过选取相同的子集. 跳过选取相同的子集.当选取完第i个数时,如果后面的数,和当前数相同,就跳过,不必对其进行递归了. class Solution { private void dfs(int n, int k, int last, int[] nums, List<Integer> cur, List<List<Integer&g

力扣——分糖果 II

排排坐,分糖果. 我们买了一些糖果 candies,打算把它们分给排好队的 n = num_people 个小朋友. 给第一个小朋友 1 颗糖果,第二个小朋友 2 颗,依此类推,直到给最后一个小朋友 n 颗糖果. 然后,我们再回到队伍的起点,给第一个小朋友 n + 1 颗糖果,第二个小朋友 n + 2 颗,依此类推,直到给最后一个小朋友 2 * n 颗糖果. 重复上述过程(每次都比上一次多给出一颗糖果,当到达队伍终点后再次从队伍起点开始),直到我们分完所有的糖果.注意,就算我们手中的剩下糖果数不

leetcode 90. 子集 II

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集). 说明:解集不能包含重复的子集. 示例: 输入: [1,2,2] 输出: [ [2], [1], [1,2,2], [2,2], [1,2], [] ]思路和上一题类似,这里在最后把多余的排列除去即可,在有重复元素的nums中,要对其排序,再进行,处理,否则会出错 1 #include<algorithm> 2 class Solution { 3 public: 4 vector<vector<int

力扣第95题 不同的二叉搜索树II

力扣第95题 不同的二叉搜索树II 给定一个整数 n,生成所有由 1 ... n 为节点所组成的二叉搜索树. struct TreeNode { int val; TreeNode *left; TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL) {} }; vector<TreeNode*> generateTree(int start, int end) { vector<TreeNode*> v

LeetCode --- 90. Subsets II

题目链接:Subsets II Given a collection of integers that might contain duplicates, S, return all possible subsets. Note: Elements in a subset must be in non-descending order. The solution set must not contain duplicate subsets. For example, If S = [1,2,2]

力扣算法题—042接雨水

1 #include"000库函数.h" 2 //一点头绪都没有 3 //然后就自己按自己的意思来一遍 4 //好像没有用算法 5 //16ms,让我激动一把 6 7 class Solution { 8 public: 9 int trap(vector<int>& height) { 10 if (height.size() < 2)return 0; 11 int s = 0;//起始点 12 int e = 0;//终止点 13 int v = 0;/

卡特兰数(Catalan number)-力扣96

卡特兰数又称卡塔兰数,英文名Catalan number,是组合数学中一个常出现在各种计数问题中出现的数列.以比利时的数学家欧仁·查理·卡塔兰 (1814–1894)的名字来命名,其前几项为 : 1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440, 9694845, 35357670, 129644790, 477638700, 1767263190, 6564120420, 244662

力扣题目汇总(加一,旋转数组,整数反转)

力扣题目汇总(加一,旋转数组,整数反转) 加一 1.题目描述 给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一. 最高位数字存放在数组的首位, 数组中每个元素只存储一个数字. 你可以假设除了整数 0 之外,这个整数不会以零开头. 示例 1: 输入: [1,2,3] 输出: [1,2,4] 解释: 输入数组表示数字 123. 示例 2: 输入: [4,3,2,1] 输出: [4,3,2,2] 解释: 输入数组表示数字 4321. 2.解题思路 #错误思路 列表最后一位加1,判断最后

力扣题目汇总(转换成小写字母,唯一摩尔斯密码,有序数组平方)

力扣题目汇总(转换成小写字母,唯一摩尔斯密码,有序数组平方) 转换成小写字母 1.题目描述 实现函数 ToLowerCase(),该函数接收一个字符串参数 str,并将该字符串中的大写字母转换成小写字母,之后返回新的字符串. 示例 1: 输入: "Hello" 输出: "hello" 示例 2: 输入: "here" 输出: "here" 示例 3: 输入: "LOVELY" 输出: "lovel