leetcode
leetcode 51 ~ 100
子集 II

子集 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

代码结果

运行时间: 32 ms, 内存: 15.2 MB


// Java Stream solution
// 思路:
// 1. 使用Java Stream生成幂集。
// 2. 使用集合来去重。
// 3. 对结果集进行排序以确保顺序。
 
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
 
public class StreamSolution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums); // 排序
        Set<List<Integer>> resultSet = IntStream.range(0, 1 << nums.length)
            .mapToObj(i -> IntStream.range(0, nums.length)
                .filter(j -> (i & (1 << j)) != 0)
                .mapToObj(j -> nums[j])
                .collect(Collectors.toList()))
            .collect(Collectors.toSet());
        return resultSet.stream().sorted((a, b) -> a.size() - b.size()).collect(Collectors.toList());
    }
}

解释

方法:

这个题解使用了回溯算法来生成所有可能的子集。首先将数组进行排序,以方便去除重复的子集。然后定义一个回溯函数,通过维护一个路径数组 path,依次将数组中的元素加入路径中,并递归调用回溯函数,直到达到终止条件。在递归过程中,通过判断当前元素是否与前一个元素相同,来避免生成重复的子集。最后返回所有生成的子集。

时间复杂度:

O(n * 2^n)

空间复杂度:

O(n * 2^n)

代码细节讲解

🦆
在nums.sort()操作之后,为什么可以通过简单的比较nums[i]与nums[i-1]来避免重复的子集?
当数组nums被排序后,相同的元素会被放置在连续的位置。在回溯过程中,通过比较当前元素nums[i]与其前一个元素nums[i-1],可以判断当前元素是否与前一个元素相同。如果相同,并且不是循环的起始元素(即i > start),则跳过当前元素,从而避免生成与前一次递归相同的子集。这种方法有效地利用了排序后元素顺序的特性,以简单且有效的方式防止了重复子集的生成。
🦆
回溯函数中的终止条件是len(path) == len(nums),这是否意味着函数只在path与nums长度相同时才停止递归?这样的设计是否必要?
实际上,终止条件len(path) == len(nums)并不是必须的,因为回溯过程在每次递归时都已经将当前path添加到结果集中。该条件似乎是多余的,因为当path长度与nums相等后,循环不会再继续执行(因为i的范围会超出nums的长度),自然就不会再有递归调用。因此,这个条件可以被移除,而不影响算法的正确性或性能。
🦆
在递归调用backtrace(path, i+1)时,参数i+1的作用是什么?这是否意味着每次递归都是从下一个元素开始?
参数i+1确保了每次递归调用开始探索的是当前元素的下一个元素,从而生成所有可能的子集组合。这是回溯算法避免重复使用相同元素的关键机制。通过从i+1开始,算法确保了在当前递归层级中,之前已使用的元素不会再次被考虑,从而能生成所有不同的组合而不重复。
🦆
为什么在path.pop()之后没有再次检查重复元素,这样做是否可能会在后续的递归中产生重复的子集?
在path.pop()操作后没有必要再次检查重复元素,因为重复的检查已经在循环的开始处通过比较nums[i]与nums[i-1]实现。当path.pop()移除最后一个元素后,回溯将继续尝试下一个可能的元素(如果存在)。因为数组已经排序,重复的元素会被前面的逻辑处理掉,不会在后续的递归中产生重复的子集。这确保了算法的效率和简洁性。

相关问题

子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

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

 

示例 1:

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

示例 2:

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

 

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10
  • nums 中的所有元素 互不相同