📅  最后修改于: 2023-12-03 14:56:54.288000             🧑  作者: Mango
这是一道算法题,需要计算一个整数数组中所有元素的按位或的结果总和。具体来说,对于数组中的任意两个元素,计算它们的按位或运算结果,然后将所有结果加起来,得到最终答案。
最直观的解法是使用两层循环枚举数组中的所有元素对,并计算它们的按位或运算结果。这种解法的时间复杂度是 $O(n^2)$。
def bitwise_or_sum(nums: List[int]) -> int:
n = len(nums)
res = 0
for i in range(n):
for j in range(i + 1, n):
res += nums[i] | nums[j]
return res
观察按位或的定义,发现它的结果比较难直接计算。但是,我们可以将按位或转换为按位与和按位异或的组合,具体如下:
$$a \texttt{ | } b = (a \texttt{ ^ } b) \texttt{ | } (a \texttt{ & } b)$$
根据这个等式,我们可以使用一层循环遍历数组中的所有元素,依次计算它与所有前面的元素的按位异或和按位与,将结果相加,得到最终答案。
def bitwise_or_sum(nums: List[int]) -> int:
n = len(nums)
res = 0
cur_or = 0 # 当前元素与之前所有元素的按位或结果
for i in range(n):
cur_or |= nums[i]
cur_xor = cur_or # 当前元素与之前所有元素的按位异或结果
for j in range(i):
cur_xor ^= nums[j]
res += cur_xor
return res
这种解法的时间复杂度是 $O(n^2)$,空间复杂度是 $O(1)$。
如果我们仔细观察按位或运算的定义,发现它具有一个重要的性质:如果 $a$ 或 $b$ 的某个二进制位为 $1$,则它们的按位或结果的该二进制位也为 $1$,否则为 $0$。也就是说,在计算按位或结果时,如果我们将每个元素的所有二进制位分开考虑,就可以在不使用按位或运算的情况下计算每一位的结果。
具体来说,我们可以从低位到高位依次计算所有元素的二进制位,统计出每个二进制位上的 $1$ 的个数。假设当前计算到第 $k$ 位,对于所有元素的第 $k$ 位,如果有 $m$ 个元素的该位为 $1$,则该位对结果的贡献是 $2^{k-1} \times m \times (n-m)$,其中 $n$ 是元素的总数。可以发现,这个式子中 $m$ 和 $n-m$ 是可以提前预处理出来的,因此我们只需要在一次遍历中统计所有二进制位的 $1$ 的个数即可。
def bitwise_or_sum(nums: List[int]) -> int:
n = len(nums)
res = 0
for k in range(32): # 计算每一位的结果
cnt = 0
for i in range(n):
if nums[i] & (1 << k):
cnt += 1
res += (1 << k) * cnt * (n - cnt)
return res
这种解法的时间复杂度是 $O(32n) = O(n)$,空间复杂度是 $O(1)$。
本题的解法涉及到按位或、按位与、按位异或等位运算的使用,同时也需要一些位运算的技巧。相比于暴力枚举,二进制拆分的方法能够大幅优化时间复杂度。