4.算法-所有数组的子集

一、 数组子集

数组子集:
输入:nums = [1,2,3]
输出:[[],[1],[1,2],[1,2,3],[2,3],[3]]

二、位运算迭代算法

先看代码:

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        List<Integer> temp = new ArrayList<>();
        int len = nums.length;
        // 1  外部循环2的len 次方循环 
        for (int loop = 0; loop < (1 << len); loop++) {
            temp.clear();
            // 内部循环 len
            for (int j = 0; j < len; j++) {
                //3 位运算判断是否子集
                if ((loop & (1 << j)) != 0) {
                    temp.add(nums[j]);
                }
            }
            result.add(new ArrayList<>(temp));
        }
        return result;
    }
}

2.1 外部循环次数

如果数组是是 [1,2,3,4] 同过计算 子集有16种情况

C n 0 + C n 1 + … + C n n = 2 n C_n^0+C_n^1+\ldots +C_n^n = 2^n Cn0+Cn1++Cnn=2n

C 4 0 + C 4 1 + … + C 4 4 = 2 4 C_4^0+C_4^1+\ldots +C_4^4 = 2^4 C40+C41++C44=24

2 4 2^4 24 =1>>4=16

接下来开始计算每种情况的子集

2.2 内部循环次数

内部循环容易理解,因为数组成员变量 1,2,3,4
子集只可能从成员中取出所以 j < 4

2.3 位运算

位运算是算法的关键,为什么位运算可以选择数组子集呢
假设数组长度是 4
那么外部循环是吧
**loop ** : 0,1,2,3,4,5,······15;

0000 ,0001, 0010,0011, 0100, 0101,0110 ,0111, 1000,1001,1010,1011,1100,1101,1110,1111
每次内部循环 j : 0,1,2,3
内存循环对应的二进制是: 0000 , 0001, 0010 ,0011

我们发现 对于外部每次循环

寻找一个数 sate 和 mask 做 & 运算可以计算出 j 是否属于 数组子集一个下标

sate 和j 的关系呢

1<< j =sate

1111 & sate 对应 C 4 4 C_4^4 C44
那么数组 四个成员下标全部命中

经过数学运算 得出 1<<j =sate
如果不能理解,大家可以直接记住这个 公式。

总结:位运算计算数组子集 设计很巧妙,利用二进制位运算完成子集成员的选择。

三、递归算法

<think>好的,用户想要找出数字列表中所有和为0的组合。我需要先理解这个问题的具体要求。通常这类问题有两种情况:三元组、二元组或者更多元素的组合。用户没有明确说明,但常见的类似问题比如LeetCode的三数之和,所以可能需要考虑多个元素的组合,但可能用户主要指的是三元组。 首先,我要回忆解决这类问题的常用方法。对于两数之和,可以用哈希表或者双指针法。如果是三数之和,通常使用排序加双指针,这样可以避免重复组合。而对于更多元素的情况,可能需要递归或回溯的方法,但时间复杂度会很高。 用户可能需要考虑重复元素的情况,比如列表中可能有多个相同的数字,但组合不能重复。例如,输入是[-1,0,1,2,-1,-4],那么输出应该包括[-1,-1,2], [-1,0,1]等,但不能有重复的组合。 接下来,我需要确定如何高效地找到所有这样的组合。三数之和的标准解法是先排序数组,然后固定一个数,用双指针在剩余部分寻找两数之和为固定数的相反数。这种方法时间复杂度是O(n²),空间复杂度O(1)(不考虑结果存储)。对于更多元素的情况,比如四数之和,可以继续扩展这种方法,但需要更多的循环或者递归。 但用户的问题没有明确组合的大小,可能需要先确认是否需要所有可能的组合,无论元素个数。但这样的话,时间复杂度会非常高,比如所有子集的情况,这显然不现实。所以可能用户需要的是三数之和的情况,或者所有可能的k数之和等于0的情况。但通常实际应用中,三数之和是比较常见的问题,所以我可能先按照三数之和来解答,并扩展说明其他情况的方法。 然后,我需要考虑如何避免重复的组合。排序之后,可以在遍历时跳过相同的元素,这样能有效避免重复。例如,在固定第一个数时,如果当前数和前一个相同,就跳过,避免重复处理。 接着,我需要编写代码示例。例如,三数之和的Python实现。这里需要排序数组,然后遍历每个元素作为第一个数,然后在其右侧使用双指针寻找另外两个数,使得三数之和为0。同时处理重复元素的情况。 另外,可能需要考虑优化,比如当固定数已经大于0时,后面的数不可能加起来为0,可以提前终止循环。 然后,生成相关问题。用户可能想知道如何扩展到更多元素的情况,比如四数之和,或者如何处理大数据量时的效率问题,或者是否有其他方法如哈希表法等,以及这些方法的优缺点。 现在,检查是否有遗漏的步骤,比如边界条件处理,比如数组长度不足的情况,或者全0的情况等。在代码中应该包含这些情况的处理。 总结,回答应包括以下部分: 1. 问题分析,可能的解法思路。 2. 详细步骤,包括排序、双指针的使用,避免重复的方法。 3. 代码示例,用Python实现。 4. 时间和空间复杂度分析。 5. 扩展讨论,比如k数之和的情况或其他优化方法。 6. 生成的相关问题,如性能优化、不同解法比较等。</think>要高效查找数字列表中所有和为0的组合,可采用排序+双指针法,以下是分步解决方案: ### 算法实现步骤 1. **数组预处理** - 先对数组进行升序排序$O(n\log n)$ - 排序后相同元素相邻,便于后续跳过重复组合 2. **三层循环结构** - 外层循环固定第一个元素$nums[i]$ - 当$nums[i] > 0$时可提前终止循环(剪枝优化) - 中循环使用左右指针$left=i+1$和$right=len(nums)-1$ - 内层通过指针移动调整三数之和$sum = nums[i]+nums[left]+nums[right]$ 3. **去重处理** - 跳过外层循环中相同值的元素`if i>0 and nums[i]==nums[i-1]` - 当找到有效组合后,跳过左右指针指向的重复元素 ```python def threeSum(nums): nums.sort() res = [] n = len(nums) for i in range(n-2): if nums[i] > 0: break # 剪枝优化 if i > 0 and nums[i] == nums[i-1]: continue # 外层去重 left, right = i+1, n-1 while left < right: total = nums[i] + nums[left] + nums[right] if total < 0: left += 1 elif total > 0: right -= 1 else: res.append([nums[i], nums[left], nums[right]]) # 内层去重 while left < right and nums[left] == nums[left+1]: left += 1 while left < right and nums[right] == nums[right-1]: right -= 1 left += 1 right -= 1 return res ``` ### 复杂度分析 - 时间复杂度:$O(n^2)$ - 排序耗时$O(n\log n)$ - 双指针遍历$O(n^2)$ - 空间复杂度:$O(1)$(不包含结果存储空间) ### 扩展应用 该算法可扩展为k数之和的通用解法,通过递归将k数之和转化为(k-1)数之和问题,但时间复杂度会上升为$O(n^{k-1})$[^1]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值