lotus

贵有恒何必三更眠五更起 最无益只怕一日曝十日寒

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

1. 题目

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

考察点:

 

 

2. 解法

解法有三种

Leetcode 78是一个关于子集合的问题,给定一个不重复的整数数组nums,返回所有可能的子集(幂集)。

有多种方法可以用Java实现,比如:

  • 使用迭代法,每次遍历数组中的一个元素,将其加入到已有的子集中,并生成新的子集。
  • 使用回溯法,每次选择或不选择数组中的一个元素,递归地构建子集,并将结果添加到列表中。
  • 使用位运算法,每个子集可以用一个二进制数表示,其中第i位为1表示选择数组中的第i个元素,为0表示不选择。遍历所有可能的二进制数,将对应的子集添加到列表中

 

解法一:使用迭代法

思路

方法一的思路是利用数学上的集合运算,如果一个集合有n个元素,那么它的子集有2^n个,可以用二进制数表示。例如,如果集合是{1,2,3},那么它的子集有8个,分别是:

  • 000: 空集
  • 001: {1}
  • 010: {2}
  • 011: {1,2}
  • 100: {3}
  • 101: {1,3}
  • 110: {2,3}
  • 111: {1,2,3}

可以看到,每次从左到右遍历一个二进制位,就相当于选择或不选择当前元素。所以,我们可以用一个循环来遍历数组中的每个元素,每次将其加入到已有的子集中,并生成新的子集。例如,当遍历到第一个元素1时,我们将其加入到空集中,得到{1},然后将空集和{1}都添加到结果列表中。当遍历到第二个元素2时,我们将其加入到空集和{1}中,得到{2}和{1,2},然后将这两个新的子集也添加到结果列表中。以此类推,直到遍历完所有元素,就得到了所有的子集。

代码逻辑

具体实现

public List<List<Integer>> subsets(int[] nums) {
    List<List<Integer>> result = new ArrayList<>();
    if (nums == null) {
        return result;
    }
    result.add(new ArrayList<>()); // 空集
    for (int num : nums) { // 遍历数组中的每个元素
        List<List<Integer>> temp = new ArrayList<>(); // 创建一个临时列表存储新生成的子集
        for (List<Integer> list : result) { // 遍历已有的子集
            List<Integer> newList = new ArrayList<>(list); // 复制一份
            newList.add(num); // 加入当前元素
            temp.add(newList); // 添加到临时列表中
        }
        result.addAll(temp); // 将临时列表中的所有子集添加到结果列表中
    }
    return result;
}

  

解法二:使用回溯法

思路

使用回溯算法的思路是,

从空集开始,每次考虑数组中的一个元素,是否加入到当前的子集中,然后递归地处理剩余的元素。

当遍历完所有元素后,将当前的子集加入到解集中。

为了避免重复,可以先对数组进行排序,然后保证每次只从当前元素之后的元素中选择。

 

代码逻辑

代码的逻辑是这样的:

  • 首先,对数组进行排序,方便去重。
  • 然后,创建一个解集res,用来存放所有的子集,以及一个临时子集subset,用来存放当前的子集。
  • 接着,调用一个回溯函数backtrack,传入数组nums,起始位置start,临时子集subset和解集res。
  • 在回溯函数中,首先将当前的子集subset复制一份并加入到解集res中。
  • 然后,从起始位置start开始遍历数组nums中的元素,对于每个元素nums[i],有两种选择:加入到当前子集中或不加入。
  • 如果选择加入到当前子集中,就将nums[i]添加到subset的末尾,然后递归地调用回溯函数,传入数组nums,下一个起始位置i+1,临时子集subset和解集res。
  • 如果选择不加入到当前子集中,就跳过这个元素,继续遍历下一个元素。
  • 在递归返回后,需要回溯,即将刚刚添加到subset的末尾的元素移除,恢复到之前的状态。
  • 这样,就可以遍历所有可能的子集,并将它们加入到解集中。

具体实现

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        // 排序数组,方便去重
        Arrays.sort(nums);
        // 创建解集和临时子集
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> subset = new ArrayList<>();
        // 调用回溯函数
        backtrack(nums, 0, subset, res);
        // 返回解集
        return res;
    }

    // 回溯函数
    private void backtrack(int[] nums, int start, List<Integer> subset, List<List<Integer>> res) {
        // 将当前子集加入到解集中
        res.add(new ArrayList<>(subset));
        // 遍历数组中的元素
        for (int i = start; i < nums.length; i++) {
            // 将当前元素加入到子集中
            subset.add(nums[i]);
            // 递归地处理剩余的元素,注意下一次的起始位置是i+1
            backtrack(nums, i + 1, subset, res);
            // 回溯,将当前元素从子集中移除
            subset.remove(subset.size() - 1);
        }
    }
}

  

解法三:使用位运算法

思路

代码逻辑

具体实现

public List<List<Integer>> subsets(int[] nums) {
    List<List<Integer>> result = new ArrayList<>();
    int n = nums.length; // 数组长度
    int m = 1 << n; // 子集个数,等于2的n次方
    for (int i = 0; i < m; i++) { // 遍历所有可能的二进制数
        List<Integer> list = new ArrayList<>(); // 创建一个空列表存储当前子集
        for (int j = 0; j < n; j++) { // 遍历数组中的每个元素
            if ((i & (1 << j)) != 0) { // 如果二进制数的第j位为1,表示选择该元素
                list.add(nums[j]); // 将该元素添加到列表中
            }
        }
        result.add(list); // 将当前列表添加到结果列表中
    }
    return result;
}

  

3. 总结

posted on 2023-04-29 23:18  白露~  阅读(9)  评论(0编辑  收藏  举报