1310. 子数组异或查询
🎯 问题描述
描述 :
给你一个正整数数组 arr(注意题目通常指非负整数),以及一个查询数组 queries,其中 queries[i] = [li, ri]。
对于每个查询 i,你必须计算 arr[li] ^ arr[li+1] ^ ... ^ arr[ri] 的结果(即从 li 到 ri 的所有元素的异或值)。
返回一个包含所有查询结果的数组。
说明 :
这是一个位运算 结合前缀和思想 的问题。
核心策略 :
异或运算满足结合律 和交换律 。
异或运算有一个极其重要的性质:自反性 ,即 a ⊕ a = 0 a \oplus a = 0 a ⊕ a = 0 。
利用这一性质,我们可以通过前缀异或数组来快速计算任意区间的异或值,类似于前缀和中的区间减法。
示例 :
示例 1 :
1 2 3 4 5 6 7 8 9 输入:arr = [1,3,4,8], queries = [[0,1],[1,2],[0,3],[3,3]] 输出:[2,7,14,8] 解释: - arr = [1,3,4,8] - 前缀异或 pre = [0, 1, 1^3=2, 2^4=6, 6^8=14] -> [0, 1, 2, 6, 14] - queries[0] = [0, 1]: pre[2] ^ pre[0] = 2 ^ 0 = 2 - queries[1] = [1, 2]: pre[3] ^ pre[1] = 6 ^ 1 = 7 (即 3^4) - queries[2] = [0, 3]: pre[4] ^ pre[0] = 14 ^ 0 = 14 - queries[3] = [3, 3]: pre[4] ^ pre[3] = 14 ^ 6 = 8
💻 解题思路
思路1:前缀异或
核心逻辑
构建前缀异或数组 pre :
pre[i+1] 表示 arr[0] 到 arr[i] 所有元素的异或值。
递推公式:pre[i+1] = pre[i] ^ arr[i]。
初始化 pre[0] = 0,方便处理从下标 0 开始的区间。
利用异或性质计算区间异或 :
我们需要计算区间 [l, r] 的异或值:a r r [ l ] ⊕ a r r [ l + 1 ] ⊕ . . . ⊕ a r r [ r ] arr[l] \oplus arr[l+1] \oplus ... \oplus arr[r] a r r [ l ] ⊕ a r r [ l + 1 ] ⊕ . . . ⊕ a r r [ r ] 。
我们知道 pre[r+1] 是 a r r [ 0 ] ⊕ . . . ⊕ a r r [ l − 1 ] ⊕ a r r [ l ] ⊕ . . . ⊕ a r r [ r ] arr[0] \oplus ... \oplus arr[l-1] \oplus arr[l] \oplus ... \oplus arr[r] a r r [ 0 ] ⊕ . . . ⊕ a r r [ l − 1 ] ⊕ a r r [ l ] ⊕ . . . ⊕ a r r [ r ] 。
我们知道 pre[l] 是 a r r [ 0 ] ⊕ . . . ⊕ a r r [ l − 1 ] arr[0] \oplus ... \oplus arr[l-1] a r r [ 0 ] ⊕ . . . ⊕ a r r [ l − 1 ] 。
利用 a ⊕ a = 0 a \oplus a = 0 a ⊕ a = 0 ,我们将两者异或:p r e [ r + 1 ] ⊕ p r e [ l ] = ( a r r [ 0 ] ⊕ . . . ⊕ a r r [ l − 1 ] ) ⊕ ( a r r [ 0 ] ⊕ . . . ⊕ a r r [ l − 1 ] ) ⊕ ( a r r [ l ] ⊕ . . . ⊕ a r r [ r ] ) pre[r+1] \oplus pre[l] = (arr[0] \oplus ... \oplus arr[l-1]) \oplus (arr[0] \oplus ... \oplus arr[l-1]) \oplus (arr[l] \oplus ... \oplus arr[r])
p r e [ r + 1 ] ⊕ p r e [ l ] = ( a r r [ 0 ] ⊕ . . . ⊕ a r r [ l − 1 ] ) ⊕ ( a r r [ 0 ] ⊕ . . . ⊕ a r r [ l − 1 ] ) ⊕ ( a r r [ l ] ⊕ . . . ⊕ a r r [ r ] )
= 0 ⊕ ( a r r [ l ] ⊕ . . . ⊕ a r r [ r ] ) = 0 \oplus (arr[l] \oplus ... \oplus arr[r])
= 0 ⊕ ( a r r [ l ] ⊕ . . . ⊕ a r r [ r ] )
= a r r [ l ] ⊕ . . . ⊕ a r r [ r ] = arr[l] \oplus ... \oplus arr[r]
= a r r [ l ] ⊕ . . . ⊕ a r r [ r ]
因此,区间 [l, r] 的异或值等于 pre[r+1] ^ pre[l]。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution : def xorQueries (self, arr: List [int ], queries: List [List [int ]] ) -> List [int ]: n = len (arr) ans = [] pre = [0 ] * (n + 1 ) for i, num in enumerate (arr): pre[i+1 ] = pre[i] ^ num for l, r in queries: c = pre[r+1 ] ^ pre[l] ans.append(c) return ans
📊 性能分析
时间复杂度 :O ( N + Q ) O(N + Q) O ( N + Q )
构建前缀异或数组:O ( N ) O(N) O ( N ) ,其中 N N N 是 arr 的长度。
处理查询:O ( Q ) O(Q) O ( Q ) ,其中 Q Q Q 是 queries 的长度。每个查询只需 O ( 1 ) O(1) O ( 1 ) 时间进行异或运算。
总体线性时间,非常高效。
空间复杂度 :O ( N ) O(N) O ( N )
需要一个长度为 N + 1 N+1 N + 1 的 pre 数组来存储前缀异或值。
结果数组 ans 占用 O ( Q ) O(Q) O ( Q ) 空间(通常不计入额外空间复杂度,因为是返回值)。
思考
异或的“减法” :
在前缀和中,区间和 = S u m ( r ) − S u m ( l − 1 ) Sum(r) - Sum(l-1) S u m ( r ) − S u m ( l − 1 ) 。
在前缀异或中,区间异或 = X o r ( r ) ⊕ X o r ( l − 1 ) Xor(r) \oplus Xor(l-1) X o r ( r ) ⊕ X o r ( l − 1 ) 。
这是因为异或运算中,加法和减法是同一个操作(即异或本身)。
pre 数组的定义 :
定义 pre[0] = 0 是关键。
如果查询区间从 0 开始(即 l=0),我们需要计算 pre[r+1] ^ pre[0]。
因为 pre[0] = 0,且 x ⊕ 0 = x x \oplus 0 = x x ⊕ 0 = x ,所以结果就是 pre[r+1],逻辑正确。
为什么不用暴力法?
暴力法对于每个查询都遍历区间,时间复杂度 O ( Q ⋅ N ) O(Q \cdot N) O ( Q ⋅ N ) 。
如果 N = 30000 , Q = 30000 N=30000, Q=30000 N = 3 0 0 0 0 , Q = 3 0 0 0 0 ,运算次数可达 9 × 1 0 8 9 \times 10^8 9 × 1 0 8 ,极易超时。
前缀异或将复杂度降为 O ( N + Q ) O(N+Q) O ( N + Q ) ,能够轻松通过。
位运算的性质 :
a ⊕ a = 0 a \oplus a = 0 a ⊕ a = 0
a ⊕ 0 = a a \oplus 0 = a a ⊕ 0 = a
结合律:( a ⊕ b ) ⊕ c = a ⊕ ( b ⊕ c ) (a \oplus b) \oplus c = a \oplus (b \oplus c) ( a ⊕ b ) ⊕ c = a ⊕ ( b ⊕ c )
这些性质是解决此类位运算区间问题的基础。