小R的子数组权值
问题描述
小R有一个长度为 n 的数组 a,她定义每个子区间 [l, r] 的权值为 a[l] | a[l+1] | ... | a[r],即该区间内所有元素的按位或运算结果。小R非常好奇,在这 n × (n + 1) / 2 个子区间中,究竟有多少种不同的权值。
她希望你能帮她计算一下,所有子区间中的不同权值总共有多少种。
测试样例
样例1:
输入:
a = [1, 2, 4]
输出:6
样例2:
输入:
a = [5, 3, 8, 1]
输出:8
样例3:
输入:
a = [1, 1]
输出:1
样例4:
输入:
a = [7, 8, 9, 10, 11]
输出:6
要解决这个问题,我们的目标是计算数组中所有子区间的不同权值的个数。每个子区间的权值是该区间内所有元素的按位“或”运算结果(即 | 运算)。对于所有的子区间,求出它们的“或”值,并统计不同的“或”值的数量。
分析思路
-
子区间定义:
- 数组
a的所有子区间[l, r],其中1 <= l <= r <= n。这意味着有n * (n + 1) / 2个子区间。
- 数组
-
子区间的“或”运算:
- 对于每一个子区间
[l, r],我们需要计算它们的元素的按位“或”结果。按位“或”运算的性质是:当我们将新的元素加入到当前的“或”结果中时,结果只会增大或者保持不变(不会减小)。
- 对于每一个子区间
-
优化方向:
- 通过对每个子区间的右端点
r(从1到n)依次扩展,保持当前子区间的“或”值。如果当前子区间[l, r]的“或”值已经存在,那么我们就不再重复计算。 - 使用集合来存储已经出现过的不同“或”值,因为集合具有去重功能。
- 通过对每个子区间的右端点
-
关键操作:
- 对于每个可能的起始位置
l,我们从r = l开始扩展,计算每个子区间[l, r]的“或”值,并将其加入到一个集合中。
- 对于每个可能的起始位置
代码实现
def solution(a):
# 用集合存储所有不同的"或"值
or_values = set()
# 遍历每个子区间的起始位置
for i in range(len(a)):
current_or = 0 # 当前子区间的"或"值
# 从i开始,扩展到所有可能的右端点
for j in range(i, len(a)):
current_or |= a[j] # 对子区间[a[i], a[j]]进行"或"运算
or_values.add(current_or) # 将结果加入集合中
# 返回集合中不同"或"值的个数
return len(or_values)
if __name__ == '__main__':
# 测试用例
print(solution([1, 2, 4]) == 6)
print(solution([5, 3, 8, 1]) == 8)
print(solution([1, 1]) == 1)
print(solution([7, 8, 9, 10, 11]) == 6)
解释:
-
外层循环:
- 对于每个起始位置
i,我们计算从位置i开始的所有子区间。
- 对于每个起始位置
-
内层循环:
- 对于每个
i,我们逐步扩展右端点j,计算当前子区间[i, j]的“或”运算结果。
- 对于每个
-
集合存储:
- 我们将每次计算得到的“或”值加入到集合
or_values中,集合会自动去重,从而确保只统计不同的“或”值。
- 我们将每次计算得到的“或”值加入到集合
-
最终结果:
- 最终返回集合
or_values的大小,即不同“或”值的个数。
- 最终返回集合
复杂度分析:
-
时间复杂度:
- 外层循环遍历所有的起始位置
i,内层循环遍历每个起始位置的所有可能的右端点j。因此,时间复杂度为 O(n^2),其中n是数组的长度。
- 外层循环遍历所有的起始位置
-
空间复杂度:
- 我们使用一个集合来存储不同的“或”值。最坏情况下,集合的大小可能接近
n * (n + 1) / 2,即所有子区间的数量。因此,空间复杂度为 O(n^2)。
- 我们使用一个集合来存储不同的“或”值。最坏情况下,集合的大小可能接近
总结
- 按位“或”运算:通过“或”运算可以理解为将两个数的二进制位进行合并,结果的每一位要么为 1,要么为 0,且当某一位有 1 时,合并后的结果该位也为 1。这个性质使得“或”运算结果不会减少。
- 子区间扩展:通过遍历每个可能的起始位置
i和结束位置j,逐步计算每个子区间的“或”值,并存储到集合中。集合的去重特性确保了最终统计的是不同的“或”值。 - 优化策略:对于每个起始位置
i,内层循环逐步扩展右端点j,避免了重复计算,因此可以更高效地处理。
启发
- 数据结构的应用:集合用于去重是一个重要的优化点,它避免了我们手动检查重复的操作,使得问题更加简洁高效。
- 按位运算的特性:理解按位“或”的性质可以帮助更好地优化算法。例如,按位“或”运算的结果不会减小,意味着我们只需要关注如何逐步扩展子区间,而无需回溯或重新计算。
- 双重循环的优化:虽然时间复杂度为 O(n²),但通过集合的去重和按位运算的特性,实际的运行时间可能远低于最坏情况。实际应用时可以尝试进一步优化或用更高效的算法处理。