子集问题,巧妙运用位运算解决

319 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

子集问题,排列问题,组合问题,细胞分裂等等,常用解法都是利用回溯算法进行解答,这里就自己问题提供一种新的解法

对于位运算不明白的也没有关系,我会用最白的话来解释什么是位运算

开始吧!

题目参考LeetCode.78子集: leetcode.cn/problems/su…

题目

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

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

示例:

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

从数学方面来讲,这道题并不难,可是怎么让自己的思想在计算机上合理的展现呢 ?

在开始解题之前先了解一些基础

子集

一个数组有n个元素,那么它的子集的长度为 2……n (2**n)

位运算

let a = 1 << 2    
let b = 5 << 3    

//  1<<2:首先将1转为2进制,再将2进制的数字向左移2位   000000001  =>  000000100   再将得到的2进制转为10进制结果输出
//  5<<3: 首先将5转为2进制,再将2进制的数字向左移3为   000000101  =>  000101000   再将得到的2进制转为10进制结果输出

console.log(a);  // 4
console.log(b);  // 40

异或逻辑:我们使用异或时,计算机会将数字首先转换为2进制,再进行计算,最后返回10进制

微信图片_20220720124232.png

let a = 7 & 4   
//  首先将7转为2进制0111,再将4转为2进制,
//       0 1 1 1
//       0 1 0 0
//  a =  0 1 0 0 


console.log(a);    //  4

我知道了子集,也知道了位运算,那么这两个有什么联系呢?

  1. 我们可以根据数组的长度 length,得出它的子集的长度 let len = 1 << length
  2. 因为 len 在2进制中,有效长度为 length+1 ,但是我们只需要遍历到 len-1 ,那么有效长度刚好为 length
  3. 我们可以从 0 遍历到 len - 1 , 我们可以将2进制上的每一位数字,当成数组中的每一个元素的状态,      即 101 为选中,0 为未选中。如下图
       nums[1,2,3]
000        []
001        [1]
010        [2]
011        [1,2]
100        [3]
101        [1,3]
110        [2,3]
111        [1,2,3]

看完这个图不知道大家是否恍然大明白,在我们遍历的过程中,其实索引的2进制已经替我们选完了所有的子集

代码

var subsets = function (nums) {
    
    let length = nums.length                 // 数组长度 
    let len = 1 << length                    // 子集长度
    let result = []                          // 创建空数组,收集结果
    
    for (let i = 0; i < len; i++) {
        let arr = []
        for (let j = 0; j < length; j++) {
            
            if (i & (1 << j)) {              // 判断当前位置元素,是否被选中,如果选中,加入arr
                arr.push(nums[j])
            }
        }
        result.push(arr)                     // 将收集到数组,加入结果
    }
    return result
};