题目链接
题目大意
描述:给定一个整数数组 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.length≤1000。
0≤nums[i]<216。
示例:
输入: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
解题思路
思路 1:枚举
最直接的方法是使用三重循环直接枚举 (i,j,k),然后再判断 nums[i] & nums[j] & nums[k] 是否为 0。但是这样做的时间复杂度为 O(n3)。
从题目中可以看出 nums[i] 的值域范围为 [0,216],而 216=65536。所以我们可以按照下面步骤优化时间复杂度:
- 先使用两重循环枚举 (i,j),计算出 nums[i] & nums[j] 的值,将其存入一个大小为 216 的数组或者哈希表 cnts 中,并记录每个 nums[i] & nums[j] 值出现的次数。
- 然后遍历该数组或哈希表,再使用一重循环遍历 k,找出所有满足 nums[k] & x==0 的 x,并将其对应数量 cnts[x] 累积到答案 ans 中。
- 最后返回答案 ans 即可。
思路 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:复杂度分析
- 时间复杂度:O(n2+216×n),其中 n 为数组 nums 的长度。
- 空间复杂度:O(216)。
思路 2:枚举 + 优化
第一步跟思路 1 一样,我们先使用两重循环枚举 (i,j),计算出 nums[i] & nums[j] 的值,将其存入一个大小为 216 的数组或者哈希表 cnts 中,并记录每个 nums[i] & nums[j] 值出现的次数。
接下来我们对思路 1 中的第二步进行优化,在思路 1 中,我们是通过枚举数组或哈希表的方式得到 x 的,这里我们换一种方法。
使用一重循环遍历 k,对于 nums[k],我们先计算出 nums[k] 的补集,即将 nums[k] 与 216−1(二进制中 16 个 1)进行按位异或操作,得到 nums[k] 的补集 com。如果 nums[k] & x==0,则 x 一定是 com 的子集。
换句话说,x 中 1 的位置一定与 nums[k] 中 1 的位置不同,如果 nums[k] 中第 m 位为 1,则 x 中第 m 位一定为 0。
接下来我们通过下面的方式来枚举子集:
- 定义子集为 sub,初始时赋值为 com,即:sub=com。
- 令 sub 减 1,然后与 com 做按位与操作,得到下一个子集,即:sub=(sub−1) & com。
- 不断重复第 2 步,直到 sub 为空集时为止。
这种方法能枚举子集的原理是:sub 减 1 会将最低位的 1 改为 0,而比这个 1 更低位的 0 都改为了 1。此时再与 com 做按位与操作,就会过保留原本高位上的 1,滤掉当前最低位的 1,并且保留比这个 1 更低位上的原有的 1,也就得到嘞下一个子集。
举个例子,比如补集 com 为 (00010110)2:
- 初始 sub=(00010110)2。
- 令其减 1 后为 (00010101)2,然后与 com 做按位与操作,得到下一个子集 sub=(00010100)2,即:(00010101)2 & (00010110)2)。
- 令其减 1 后为 (00010011)2,然后与 com 做按位与操作,得到下一个子集 sub=(00010010)2,即: (00010011)2 & (00010110)2。
- 令其减 1 后为 (00010001)2,然后与 com 做按位与操作,得到下一个子集 sub=(00010000)2,即:(00010001)2 & (00010110)2。
- 令其减 1 后为 (00001111)2,然后与 com 做按位与操作,得到下一个子集 sub=(00000110)2,即:(00001111)2 & (00010110)2。
- 令其减 1 后为 (00000101)2,然后与 com 做按位与操作,得到下一个子集 sub=(00000100)2,即:(00000101)2 & (00010110)2。
- 令其减 1 后为 (00000011)2,然后与 com 做按位与操作,得到下一个子集 sub=(00000010)2,即:(00000011)2 & (00010110)2。
- 令其减 1 后为 (00000001)2,然后与 com 做按位与操作,得到下一个子集 sub=(00000000)2,即:(00000001)2 & (00010110)2。
- sub 变为了空集。
思路 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
sub = com
while True:
ans += cnts[sub]
if sub == 0:
break
sub = (sub - 1) & com
return ans
思路 2:复杂度分析
- 时间复杂度:O(n2+216×n),其中 n 为数组 nums 的长度。
- 空间复杂度:O(216)。
参考资料