2Brute2Best
🔥0

XOR Queries of a Subarray

Medium·LeetCode #1310Basic Anchor
LeetCode
30-Second Mental ModelRange bitwise properties — prefix XOR (static range query)
Complexity:O(N · Q)→O(N + Q)
Key Insight

Build prefix, where prefix[i] is the XOR of everything before index i, with prefix[0] = 0. Then the XOR of arr[l..r] is prefix[r + 1] ^ prefix[l]. The part before l is in both prefixes, and XOR-ing it twice cancels it out. Each query is then a single XOR.

Core Validity Invariant

prefix[i] always means the XOR of everything strictly before index i. Then arr[l..r] is prefix[r + 1] ^ prefix[l], because the part before l appears in both and cancels.

01

Problem

You get an array arr of positive integers and a list of queries. Each query is a pair [left, right]. The answer to a query is the XOR of the numbers from arr[left] to arr[right], both ends included: arr[left] ^ arr[left + 1] ^ … ^ arr[right].

Return one answer for each query, in the same order as the queries.

Input

An array arr of positive integers, and a list queries of [left, right] pairs.

Output

An array with one answer per query, in the same order.

Example 1
Input: arr = [1, 3, 4, 8], queries = [[0, 1], [1, 2], [0, 3], [3, 3]]
Output: [2, 7, 14, 8]

1 ^ 3 = 2 (0001 ^ 0011 = 0010). 3 ^ 4 = 7. 1 ^ 3 ^ 4 ^ 8 = 14. A range of one number is that number, so the last answer is 8.

Example 2
Input: arr = [4, 8, 2, 10], queries = [[2, 3], [1, 3], [0, 0], [0, 3]]
Output: [8, 0, 4, 4]

2 ^ 10 = 8. 8 ^ 2 ^ 10 = 0. A single 4 is 4. 4 ^ 8 ^ 2 ^ 10 = 4.

Constraints
  • • 1 ≤ arr.length ≤ 3 · 10^4 and 1 ≤ queries.length ≤ 3 · 10^4
  • • 1 ≤ arr[i] ≤ 10^9
  • • queries[i] has exactly 2 numbers: [left, right]
  • • 0 ≤ left ≤ right < arr.length
02

First, think yourself

Before opening any solution, try to answer these on paper. Use hints only when stuck — each hint is counted.

  1. 1.

    Range Sum Query asked for sums. What should replace the adding here, and what happens when a value is XOR-ed with itself?

  2. 2.

    If three queries all cover index 1, how many times does the brute force XOR arr[1] in total?

  3. 3.

    Can I store one value per position that says "everything up to here"? How many of those values would I need?

  4. 4.

    If I know the XOR of everything up to r and the XOR of everything before l, how do I get the middle part?

  5. 5.

    Is XOR's inverse the same operation as subtraction? What does x ^ x equal?

03

Brute force approach

05

Optimized approach

06

Why does this algorithm work?

07

Dry run

08

All approaches compared

ApproachIdeaTimeSpace
Brute ForceFor each query, XOR arr[left..right] from scratchO(N · Q)O(1)
OptimalBuild prefix XORs once; each query is prefix[r+1] ^ prefix[l]O(N + Q)O(N)
09

Why does this belong to this pattern?

Clues in the problem statement
  • “XOR of arr[left] … arr[right]”A range of one array. Like a range sum, it is built from two prefix totals.
  • “many queries, the array never changes”Compute the prefix XORs once. After that each query is O(1).
  • “XOR, not addition”XOR is its own inverse, so the subtraction step of prefix sums becomes another XOR.
Static array+many range queries+XOR of a range = prefix[r+1] ^ prefix[l]→Range bitwise properties — prefix XOR (static range query)

Range Sum Query - Immutable has the same shape with +: a range is the difference of two prefixes. Here XOR is its own inverse, so the "subtract" step becomes another XOR: x ^ x = 0 removes the shared part. Unlike the next question, this one has an array and many queries, so it needs a prefix array. The next question has no array at all; it asks about a range of consecutive numbers.

10

Don't make this mistake

Subtracting prefixes, like Range Sum Query
✗ return prefix[right + 1] - prefix[left];
✓ return prefix[right + 1] ^ prefix[left];

This is the pattern-confusion trap. The code looks the same as Range Sum Query, so the - slips in. XOR is not subtraction. On [1, 3, 4, 8], query [1, 2]: prefix[3] − prefix[1] = 6 − 1 = 5, but the answer is 3 ^ 4 = 7. The XOR version gives 6 ^ 1 = 0110 ^ 0001 = 0111 = 7.

Off by one with the 0-based prefix
✗ return prefix[r] ^ prefix[l];
✓ return prefix[r + 1] ^ prefix[l];

prefix[i] stops before index i, so prefix[r] leaves out arr[r]. On [1, 3, 4, 8], query [0, 1]: prefix[1] ^ prefix[0] = 1 ^ 0 = 1, which is just arr[0]. The answer is 1 ^ 3 = 2, and prefix[r + 1] = prefix[2] = 2 gives it.

Prefix array one slot too short
✗ vector<int> prefix(n, 0); // n slots: indices 0 … n-1 for (int i = 0; i < n; i++) prefix[i + 1] = prefix[i] ^ arr[i]; // writes prefix[n]
✓ vector<int> prefix(n + 1, 0); // n + 1 slots: indices 0 … n

prefix[n] is the XOR of the whole array and needs its own slot. Writing to prefix[n] on a vector of size n is undefined behaviour: it may crash, or silently overwrite memory next to the vector. The leading 0 is what makes the size n + 1.

11

Want to implement this?

Write it yourself on LeetCode, then come back and compare your code with the solutions above.

Open coding workspace
How did it go?
Beyond The LeetCode Problem

How this concept connects to System Design & C++ Systems

Bridge DSA with production engineering

Top engineering interviews test whether you understand how algorithms like Bit Manipulation translate into production architectures and low-level memory mechanics.