按位与为0的三元组【详解】

这个题目我也是绕了很久才想明白其核心的思想,以下给出我对于该题目解法的理解。
关键字:位运算、枚举+子集优化

题目:按位与为0的三元组
给你一个整数数组 nums ,返回其中 按位与三元组 的数目。
按位与三元组 是由下标 (i, j, k) 组成的三元组,并满足下述全部条件:

0 <= i < nums.length
0 <= j < nums.length
0 <= k < nums.length
nums[i] & nums[j] & nums[k] == 0 ,其中 & 表示按位与运算符。

示例 1:
输入:nums = [2,1,3]
输出:12
解释:可以选出如下 i, j, k 三元组:
(i=0, j=0, k=1) : 2 & 2 & 1
(i=0, j=1, k=0) : 2 & 1 & 2
(i=0, j=1, k=1) : 2 & 1 & 1
(i=0, j=1, k=2) : 2 & 1 & 3
(i=0, j=2, k=1) : 2 & 3 & 1
(i=1, j=0, k=0) : 1 & 2 & 2
(i=1, j=0, k=1) : 1 & 2 & 1
(i=1, j=0, k=2) : 1 & 2 & 3
(i=1, j=1, k=0) : 1 & 1 & 2
(i=1, j=2, k=0) : 1 & 3 & 2
(i=2, j=0, k=1) : 3 & 2 & 1
(i=2, j=1, k=0) : 3 & 1 & 2

示例 2:
输入:nums = [0,0,0]
输出:27

思维流程:

观察示例一,我们可以很自然的想到三重循环的暴力解法,但是这种方式显然是不行的。参考了官方的解法和排行第二的解法,以下给出一种更为直观和通俗的理解方法。
(1) 二进制子集的概念
把二进制数看成集合,二进制从低到高第 i 位为 1 表示 i 在集合中,为 0 表示 i 不在集合中,例如 a = 1101 --> { 0, 2, 3 }。
(2)补集的概念
A & B = 0,相当于用二进制子集表示的A集合与B集合没有交集,也可以理解为B是A的补集的子集,或者A是B补集的子集。本题中 U = { 0,1,2,…,15 } ,全集对应的数字就是 0xffff 。一个数字异或 0xffff 就可以得到它的补集。

懂了这两个概念再顺一遍整体的思维过程:
先写一个O(n^2)的枚举,计算所有的nums[ i ] & nums[ j ] 。它们相与的结果,也就是两个集合的交,一定是在全集U中的,所以需要一个 2^16 大小的数组,用来保存每种相与集合出现的次数。
然后再枚举全集中每种集合的补集,如果在之前的计算结果中出现了当前位置的补集,那么反向思考之前计算过的集合和当前枚举的集合互为补集,就构成了一个三元组,这三个集合相与就是0。

注意: 考虑空集 当 s = 0 时,再减一,又会回到全1的情况,补码的性质。

示例代码:

class Solution {
public:
    int countTriplets(vector<int> &nums) {
        int cnt[1 << 16]{};
        //先枚举两个集合相与的结果
        for (int x : nums)
            for (int y : nums)
            //相与后的结果对应数组的下标,数组中保存该集合出现的次数
                ++cnt[x & y];
        int ans = 0;
        for (int m : nums) {
        	//m表示全集
            m ^= 0xffff;
            //利用s变量,枚举所有可能的子集,s从全集开始取
            int s = m;
            do { // 枚举 m 的子集(包括空集)
                ans += cnt[s];
                //(s-1)&m 表示数组中从右往左,下一个集合的补集
                //如果该补集在第一个循环中计算中出现过,那么就形成了按位与为0的三元组
                s = (s - 1) & m;
            } while (s != m);
        }
        return ans;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值