0982. 按位与为零的三元组
大约 5 分钟
0982. 按位与为零的三元组
- 标签:位运算、数组、哈希表
- 难度:困难
题目链接
题目大意
描述:给定一个整数数组 。
要求:返回其中「按位与三元组」的数目。
说明:
按位与三元组:由下标 组成的三元组,并满足下述全部条件:
- 。
- 。
- 。
- ,其中 表示按位与运算符。
。
。
示例:
- 示例 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:枚举
最直接的方法是使用三重循环直接枚举 ,然后再判断 是否为 。但是这样做的时间复杂度为 。
从题目中可以看出 的值域范围为 ,而 。所以我们可以按照下面步骤优化时间复杂度:
- 先使用两重循环枚举 ,计算出 的值,将其存入一个大小为 的数组或者哈希表 中,并记录每个 值出现的次数。
- 然后遍历该数组或哈希表,再使用一重循环遍历 ,找出所有满足 的 ,并将其对应数量 累积到答案 中。
- 最后返回答案 即可。
思路 1:代码
class Solution:
def countTriplets(self, nums: List[int]) -> int:
states = 1 << 16
cnts = [0 for _ in range(states)]
for num_x in nums:
for num_y in nums:
cnts[num_x & num_y] += 1
ans = 0
for num in nums:
for x in range(states):
if num & x == 0:
ans += cnts[x]
return ans
思路 1:复杂度分析
- 时间复杂度:,其中 为数组 的长度。
- 空间复杂度:。
思路 2:枚举 + 优化
第一步跟思路 1 一样,我们先使用两重循环枚举 ,计算出 的值,将其存入一个大小为 的数组或者哈希表 中,并记录每个 值出现的次数。
接下来我们对思路 1 中的第二步进行优化,在思路 1 中,我们是通过枚举数组或哈希表的方式得到 的,这里我们换一种方法。
使用一重循环遍历 ,对于 ,我们先计算出 的补集,即将 与 (二进制中 个 )进行按位异或操作,得到 的补集 。如果 ,则 一定是 的子集。
换句话说, 中 的位置一定与 中 的位置不同,如果 中第 位为 ,则 中第 位一定为 。
接下来我们通过下面的方式来枚举子集:
- 定义子集为 ,初始时赋值为 ,即:。
- 令 减 ,然后与 做按位与操作,得到下一个子集,即:。
- 不断重复第 步,直到 为空集时为止。
这种方法能枚举子集的原理是: 减 会将最低位的 改为 ,而比这个 更低位的 都改为了 。此时再与 做按位与操作,就会过保留原本高位上的 ,滤掉当前最低位的 ,并且保留比这个 更低位上的原有的 ,也就得到嘞下一个子集。
举个例子,比如补集 为 :
- 初始 。
- 令其减 后为 ,然后与 做按位与操作,得到下一个子集 ,即:)。
- 令其减 后为 ,然后与 做按位与操作,得到下一个子集 ,即: 。
- 令其减 后为 ,然后与 做按位与操作,得到下一个子集 ,即:。
- 令其减 后为 ,然后与 做按位与操作,得到下一个子集 ,即:。
- 令其减 后为 ,然后与 做按位与操作,得到下一个子集 ,即:。
- 令其减 后为 ,然后与 做按位与操作,得到下一个子集 ,即:。
- 令其减 后为 ,然后与 做按位与操作,得到下一个子集 ,即:。
- 变为了空集。
思路 2:代码
class Solution:
def countTriplets(self, nums: List[int]) -> int:
states = 1 << 16
cnts = [0 for _ in range(states)]
for num_x in nums:
for num_y in nums:
cnts[num_x & num_y] += 1
ans = 0
for num in nums:
com = num ^ 0xffff # com: num 的补集
sub = com # sub: 子集
while True:
ans += cnts[sub]
if sub == 0:
break
sub = (sub - 1) & com
return ans
思路 2:复杂度分析
- 时间复杂度:,其中 为数组 的长度。
- 空间复杂度:。