Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1310. XOR Queries of a Subarray - Leetcode Solution

Problem Description

You are given an integer array arr and an array queries where each queries[i] = [L_i, R_i] represents a subarray from index L_i to R_i (inclusive). For each query, compute the bitwise XOR of the elements in the subarray arr[L_i] ^ arr[L_i+1] ^ ... ^ arr[R_i].

  • Input:
    • arr: a list of integers
    • queries: a list of pairs [L_i, R_i]
  • Output:
    • A list of integers, where the i-th integer is the XOR of the subarray defined by queries[i]

Constraints:

  • 1 <= arr.length <= 3 * 10^4
  • 1 <= queries.length <= 3 * 10^4
  • 0 <= arr[i] <= 10^9
  • 0 <= L_i <= R_i < arr.length

Thought Process

At first glance, the most straightforward way to solve this problem is to compute the XOR for each query by iterating through the specified subarray. For each query [L_i, R_i], we would loop from L_i to R_i and XOR all the elements. While this works, it is inefficient if there are many queries or the subarrays are large, leading to a time complexity of O(Q * N) where Q is the number of queries and N is the array length.

To optimize, we should look for patterns or data structures that let us compute the XOR of any subarray in constant time. One key property of XOR is that it is associative and its own inverse: a ^ a = 0 and a ^ 0 = a. This suggests we can use prefix XORs, much like prefix sums for sum queries.

Solution Approach

To efficiently answer multiple XOR subarray queries, we use a prefix XOR array. Here’s how:

  1. Build the Prefix XOR Array:
    • Create an array prefix where prefix[0] = 0.
    • For each index i (1-based), prefix[i] = prefix[i-1] ^ arr[i-1].
    • This means prefix[i] holds the XOR of all elements from arr[0] to arr[i-1].
  2. Answer Queries in Constant Time:
    • For a query [L, R], the XOR of arr[L]...arr[R] is prefix[R+1] ^ prefix[L].
    • This works because the XOR cancels out the prefix up to L-1, leaving just the XOR of the subarray.
  3. Iterate Through Queries:
    • For each query, compute prefix[R+1] ^ prefix[L] and store the result.
    • Return the list of results.

This approach preprocesses the array in O(N) time and answers each query in O(1), making the total time O(N + Q).

Example Walkthrough

Input:

  • arr = [1, 3, 4, 8]
  • queries = [[0,1],[1,2],[0,3],[3,3]]

  1. Compute prefix XOR:
    • prefix[0] = 0
    • prefix[1] = 0 ^ 1 = 1
    • prefix[2] = 1 ^ 3 = 2
    • prefix[3] = 2 ^ 4 = 6
    • prefix[4] = 6 ^ 8 = 14

    So prefix = [0, 1, 2, 6, 14]

  2. Answer queries:
    • [0,1]: prefix[2] ^ prefix[0] = 2 ^ 0 = 2 (1^3)
    • [1,2]: prefix[3] ^ prefix[1] = 6 ^ 1 = 7 (3^4)
    • [0,3]: prefix[4] ^ prefix[0] = 14 ^ 0 = 14 (1^3^4^8)
    • [3,3]: prefix[4] ^ prefix[3] = 14 ^ 6 = 8 (8)

Output: [2, 7, 14, 8]

Code Implementation

class Solution:
    def xorQueries(self, arr, queries):
        prefix = [0]
        for num in arr:
            prefix.append(prefix[-1] ^ num)
        result = []
        for l, r in queries:
            result.append(prefix[r+1] ^ prefix[l])
        return result
      
class Solution {
public:
    vector<int> xorQueries(vector<int>& arr, vector<vector<int>>& queries) {
        vector<int> prefix(arr.size() + 1, 0);
        for (int i = 0; i < arr.size(); ++i) {
            prefix[i+1] = prefix[i] ^ arr[i];
        }
        vector<int> result;
        for (auto& q : queries) {
            int l = q[0], r = q[1];
            result.push_back(prefix[r+1] ^ prefix[l]);
        }
        return result;
    }
};
      
class Solution {
    public int[] xorQueries(int[] arr, int[][] queries) {
        int n = arr.length;
        int[] prefix = new int[n + 1];
        for (int i = 0; i < n; i++) {
            prefix[i+1] = prefix[i] ^ arr[i];
        }
        int[] res = new int[queries.length];
        for (int i = 0; i < queries.length; i++) {
            int l = queries[i][0], r = queries[i][1];
            res[i] = prefix[r+1] ^ prefix[l];
        }
        return res;
    }
}
      
var xorQueries = function(arr, queries) {
    let prefix = [0];
    for (let num of arr) {
        prefix.push(prefix[prefix.length - 1] ^ num);
    }
    let res = [];
    for (let [l, r] of queries) {
        res.push(prefix[r + 1] ^ prefix[l]);
    }
    return res;
};
      

Time and Space Complexity

  • Brute-force approach:
    • For each query, loop through the subarray and compute the XOR.
    • Time complexity: O(Q * N) in the worst case, where Q is the number of queries and N is the array length.
    • Space complexity: O(1) extra (not counting output).
  • Optimized prefix XOR approach:
    • Building the prefix array: O(N)
    • Each query: O(1)
    • Total time: O(N + Q)
    • Space: O(N) for the prefix array

The optimized approach is much faster and well-suited for large inputs.

Summary

This problem demonstrates how prefix computations can transform repeated, expensive operations into constant-time lookups. By leveraging the properties of XOR and building a prefix XOR array, we can answer any subarray XOR query in constant time after a single linear pass over the input. This technique is efficient, easy to implement, and highlights a powerful pattern for range query problems.