文章

[LeetCode] 每日一题 90. 子集 II

题目链接

https://leetcode.cn/problems/subsets-ii

题目描述

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的

子集(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

子集

数组的 子集 是从数组中选择一些元素(可能为空)。

示例输入

示例 1

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

示例 2

输入:nums = [0]
输出:[[],[0]]

提示

  • 1 <= nums.length <= 10

  • -10 <= nums[i] <= 10

解题思路

本题要求返回一个整数数组的所有可能子集,其中可能包含重复的元素。为了避免重复的子集,我们需要在生成子集时采取有效的去重措施

回溯算法

  1. 排序:首先将输入数组排序,这样相同的元素会被放在相邻的位置,方便去重

  2. 回溯:从每个元素开始,尝试包括当前元素或者不包括它。每次尝试时,记录当前的路径

  3. 去重条件:如果当前元素和前一个元素相同且前一个元素没有被使用过,说明当前元素是重复的,因此跳过当前元素。这一步有效地避免了重复子集的产生

  4. 路径存储:每生成一个子集,就将其加入到结果集中。需要注意的是,路径的内容是以当前状态为基础不断扩展的

代码实现

class Solution {
    boolean[] used;
    List<Integer> path = new ArrayList<>();
    List<List<Integer>> res = new ArrayList<>();

    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        used = new boolean[nums.length];
        backtrack(nums, 0);
        return res;
    }

    private void backtrack(int[] nums, int startIndex) {
        res.add(new ArrayList<>(path));
        for (int i = startIndex; i < nums.length; i++) {
            if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
                continue;
            }
            used[i] = true;
            path.add(nums[i]);
            backtrack(nums, i + 1);
            path.remove(path.size() - 1);
            used[i] = false;
        }
    }
}

复杂度分析

  • 时间复杂度:O(2^n),其中 n 是数组的长度。在最坏情况下,我们会生成所有的子集,而子集的数量是 2^n。因此,时间复杂度为 O(2^n)

  • 空间复杂度:O(n),用于存储路径和递归栈的空间。递归的深度最多为 n,而路径的长度也最大为 n,因此空间复杂度是 O(n)

总结

本题通过回溯算法生成所有可能的子集,并且通过排序和去重条件有效地避免了重复子集的出现。回溯算法能够递归地探索每一种可能的选择,通过逐步增加元素到路径中,并在每次递归结束后撤销选择。

这种方式不仅能解决问题,而且具有较高的效率,特别是在处理有重复元素的情况下,通过合理的去重条件使得算法避免了重复计算。

希望这篇分享能为你带来启发!如果你有任何问题或建议,欢迎在评论区留言,与我共同交流探讨。

License:  CC BY 4.0