[2044. 统计按位或能得到最大值的子集数目]

(力扣)

前提:

这个数组的子集进行或运算能得到最大的数肯定是全部数一起或,秉承或运算不要白不要的原则。

第一种方法:二进制枚举

这种方法其实我觉得是为了引出下面两种更方便的方法提出的

用二进制位来存储各个数是否加入或运算

假如nums数组是n个数字,那么我们只需要遍历1<<16种情况

每一种情况中,我们根据位数上的数是否是1来确定是否要加入或运算

public int countMaxOrSubsets1(int[] nums) {
    int kind=1<<nums.length;
    int max=0;
    for (int num : nums) {
        max|=num;           //最大数肯定是全部进行或
    }
    int ans=0;
    for (int i = 1; i < kind; i++) {
        int temp=0;
        for (int j = 0; j < nums.length; j++) {
            if((i>>j&1)==1) temp|=nums[j];          //j位为1代表j要参与异或
        }
        if(temp==max) ans++;                        //如果或之后的值为最大值 ans++
    }
    return ans;
}

第二种 状态压缩DP

分析第一种方法,可以发现我们重复计算了 相同的数进行或运算

[例子]: 10101 10100 这两种情况,10100知道了,自然就可以知道10101

得到最右边一位的1的位置

0000100 i

0101100 当前的选择代表的数 cur

0101000 pre

result[cur] 代表当前选择所得到的或操作结果

有公式:

result[cur]=result[pre]|nums[i]

所以我们只要保证i的选择是从小到大就行了

static HashMap<Integer,Integer> map;
static {
    map=new HashMap<>();
    for (int i = 0; i < 16; i++) {
        map.put(1<<i,i);
    }
}
public int countMaxOrSubsets2(int[] nums){
    int max=0;int ans=0;
    for (int num : nums) {
        max|=num;           //最大数肯定是全部进行或
    }
    int kind=1<<nums.length;
    int[] result=new int[kind+1];
    Arrays.fill(result,0);
    //用来存放前面遍历过的情况的结果,例如下标3代表00011即只选第一个和第二个的情况
    for (int i = 1; i < kind; i++) {
        int num010=i&-i;//得到最右边的1(为什么是最右边,看note.md)
        int index=map.get(num010);
        int pre=i-num010;
        result[i]=result[pre]|nums[index];
        if(result[i]==max) ans++;
    }
    return ans;
}

第二种做法,有一个细节是找到最右边的位,这个点我思考了一会,觉得左边会更合理,

比如 得到result[00001]

那么自然 00011就是选择2与result[00001]进行或操作

但是比起求最左边的1,求最右边的1更加方便,并且我们只要知道小的就可以求大的

因为pre肯定比cur要小

result[cur]=result[pre]|nums[i]

第三种 dfs

依靠递归来记录每次的结果

int max=0;int ans=0;
public int countMaxOrSubsets(int[] nums){
    for (int num : nums) {
        max|=num;           //最大数肯定是全部进行或
    }
    dfs(0,0,nums);
    return ans;
}
public void dfs(int i,int preResult,int[] nums){
    if(i== nums.length&&preResult==max) ans++;
    //一定要等到所有情况都遍历完了再判断,不然前面判断,后面又判断,会多算那些后面是0的情况
    else if(i<nums.length){ //if-else if这两个分支不是非此即彼的情况:i==nums.length but preResult!=max
        dfs(i + 1, preResult, nums);
        dfs(i + 1, preResult | nums[i], nums);
    }
​
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值