3277. Maximum XOR Score Subarray Queries
Problem Description
You are given an array nums
of n
integers and a 2D integer array queries
of size q
, where each query is represented as queries[i] = [li, ri]
.
The problem asks you to find the maximum XOR score of any subarray within the range nums[li..ri]
for each query.
The XOR score of an array is calculated through a specific process:
- Start with an array
a
- Repeatedly apply these operations until only one element remains:
- For all indices
i
except the last one, simultaneously replacea[i]
witha[i] XOR a[i + 1]
- Remove the last element of the array
- For all indices
- The final remaining element is the XOR score
For example, if we have an array [a, b, c, d]
:
- First operation:
[a XOR b, b XOR c, c XOR d]
- Second operation:
[(a XOR b) XOR (b XOR c), (b XOR c) XOR (c XOR d)]
- Third operation:
[((a XOR b) XOR (b XOR c)) XOR ((b XOR c) XOR (c XOR d))]
- This final value is the XOR score
For each query [l, r]
, you need to:
- Consider all possible subarrays within
nums[l..r]
- Calculate the XOR score for each subarray
- Return the maximum XOR score among all these subarrays
The output should be an array answer
of size q
where answer[i]
contains the maximum XOR score for query i
.
Intuition
Let's first understand what happens when we compute the XOR score. If we trace through the operations for a subarray, we notice a pattern. For an array [a, b, c]
:
- After first operation:
[a XOR b, b XOR c]
- After second operation:
[(a XOR b) XOR (b XOR c)]
=[a XOR c]
This reveals that the XOR score follows a recursive pattern similar to Pascal's triangle, but with XOR operations instead of additions.
The key insight is that we can compute the XOR score of any subarray nums[i..j]
using the XOR scores of its smaller subarrays. Specifically, if we know the XOR score of nums[i..j-1]
and nums[i+1..j]
, we can compute the XOR score of nums[i..j]
by XORing these two values:
f[i][j] = f[i][j-1] XOR f[i+1][j]
This works because:
f[i][j-1]
represents the result after applying the XOR operations on elements from indexi
toj-1
f[i+1][j]
represents the result after applying the XOR operations on elements from indexi+1
toj
- When we XOR these two values, we get the result for the full range
[i, j]
Now, for each query [l, r]
, we need the maximum XOR score among all subarrays within this range. Instead of recalculating for each query, we can precompute another DP table g[i][j]
that stores the maximum XOR score for any subarray within nums[i..j]
.
The maximum value g[i][j]
can be:
- The XOR score of the current range itself:
f[i][j]
- The maximum from the range excluding the last element:
g[i][j-1]
- The maximum from the range excluding the first element:
g[i+1][j]
Therefore: g[i][j] = max(f[i][j], g[i][j-1], g[i+1][j])
By precomputing both tables, we can answer each query in O(1) time by simply looking up g[l][r]
.
Learn more about Dynamic Programming patterns.
Solution Approach
We implement the solution using dynamic programming with two 2D tables:
- Table
f[i][j]
: Stores the XOR score of the subarraynums[i..j]
- Table
g[i][j]
: Stores the maximum XOR score among all subarrays withinnums[i..j]
Step 1: Initialize the DP tables
Create two n × n
matrices where n
is the length of the input array:
f = [[0] * n for _ in range(n)]
g = [[0] * n for _ in range(n)]
Step 2: Fill the DP tables bottom-up
We iterate from the bottom-right to top-left of the matrix (i.e., i
from n-1
to 0
). For each starting position i
, we consider all ending positions j
from i
to n-1
:
for i in range(n - 1, -1, -1):
f[i][i] = g[i][i] = nums[i] # Base case: single element
for j in range(i + 1, n):
f[i][j] = f[i][j - 1] ^ f[i + 1][j]
g[i][j] = max(f[i][j], g[i][j - 1], g[i + 1][j])
The base case is when i == j
, meaning we have a single element. In this case:
- The XOR score is the element itself:
f[i][i] = nums[i]
- The maximum is also the element itself:
g[i][i] = nums[i]
For larger subarrays where j > i
:
- Calculate the XOR score using the recurrence relation:
f[i][j] = f[i][j-1] XOR f[i+1][j]
- Calculate the maximum by taking the largest among:
- Current XOR score:
f[i][j]
- Maximum in the range without the last element:
g[i][j-1]
- Maximum in the range without the first element:
g[i+1][j]
- Current XOR score:
Step 3: Answer the queries
Once both tables are populated, we can answer each query in constant time:
return [g[l][r] for l, r in queries]
For each query [l, r]
, we simply look up g[l][r]
which contains the precomputed maximum XOR score for all subarrays within nums[l..r]
.
Time Complexity: O(n²)
for preprocessing + O(q)
for answering queries, where n
is the length of the array and q
is the number of queries.
Space Complexity: O(n²)
for storing the two DP tables.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with a small example to understand how the DP tables are built.
Input: nums = [3, 8, 2]
, queries = [[0, 2]]
We'll build two tables:
f[i][j]
: XOR score of subarray from index i to jg[i][j]
: Maximum XOR score among all subarrays within range [i, j]
Step 1: Initialize 3×3 tables
f = [[0, 0, 0], g = [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]] [0, 0, 0]]
Step 2: Fill tables bottom-up (i from 2 to 0)
When i = 2:
- Base case:
f[2][2] = g[2][2] = nums[2] = 2
When i = 1:
- Base case:
f[1][1] = g[1][1] = nums[1] = 8
- For j = 2:
f[1][2] = f[1][1] ^ f[2][2] = 8 ^ 2 = 10
g[1][2] = max(f[1][2], g[1][1], g[2][2]) = max(10, 8, 2) = 10
When i = 0:
- Base case:
f[0][0] = g[0][0] = nums[0] = 3
- For j = 1:
f[0][1] = f[0][0] ^ f[1][1] = 3 ^ 8 = 11
g[0][1] = max(f[0][1], g[0][0], g[1][1]) = max(11, 3, 8) = 11
- For j = 2:
f[0][2] = f[0][1] ^ f[1][2] = 11 ^ 10 = 1
g[0][2] = max(f[0][2], g[0][1], g[1][2]) = max(1, 11, 10) = 11
Final tables:
f = [[3, 11, 1], g = [[3, 11, 11], [0, 8, 10], [0, 8, 10], [0, 0, 2]] [0, 0, 2]]
Let's verify f[0][2] = 1
is correct:
- Original array:
[3, 8, 2]
- First operation:
[3^8, 8^2]
=[11, 10]
- Second operation:
[11^10]
=[1]
✓
Step 3: Answer queries
For query [0, 2]
: return g[0][2] = 11
This means the maximum XOR score among all subarrays in nums[0..2]
is 11, which comes from the subarray [3, 8]
(as we calculated f[0][1] = 11
).
Solution Implementation
1class Solution:
2 def maximumSubarrayXor(
3 self, nums: List[int], queries: List[List[int]]
4 ) -> List[int]:
5 """
6 Find the maximum XOR value for each queried subarray.
7
8 Args:
9 nums: List of integers
10 queries: List of [left, right] index pairs representing subarrays
11
12 Returns:
13 List of maximum XOR values for each query
14 """
15 n = len(nums)
16
17 # dp_xor[i][j] stores the XOR of all elements in the XOR triangle
18 # formed by the subarray nums[i:j+1]
19 dp_xor = [[0] * n for _ in range(n)]
20
21 # max_xor[i][j] stores the maximum XOR value that can be obtained
22 # from any sub-triangle within the range [i, j]
23 max_xor = [[0] * n for _ in range(n)]
24
25 # Build the DP tables from bottom to top, left to right
26 for i in range(n - 1, -1, -1):
27 # Base case: single element
28 dp_xor[i][i] = nums[i]
29 max_xor[i][i] = nums[i]
30
31 # Process all subarrays starting at index i
32 for j in range(i + 1, n):
33 # Calculate XOR for current subarray using the XOR triangle property
34 # dp_xor[i][j] = dp_xor[i][j-1] XOR dp_xor[i+1][j]
35 dp_xor[i][j] = dp_xor[i][j - 1] ^ dp_xor[i + 1][j]
36
37 # Update maximum XOR value for range [i, j]
38 # It's either the current XOR value, or the maximum from
39 # the left subrange [i, j-1], or the right subrange [i+1, j]
40 max_xor[i][j] = max(
41 dp_xor[i][j],
42 max_xor[i][j - 1],
43 max_xor[i + 1][j]
44 )
45
46 # Answer each query by looking up the precomputed maximum XOR value
47 return [max_xor[left][right] for left, right in queries]
48
1class Solution {
2 public int[] maximumSubarrayXor(int[] nums, int[][] queries) {
3 int n = nums.length;
4
5 // dp[i][j] stores the XOR pyramid value for subarray from index i to j
6 int[][] dp = new int[n][n];
7
8 // maxXor[i][j] stores the maximum XOR value in subarray from index i to j
9 int[][] maxXor = new int[n][n];
10
11 // Build the DP table from bottom to top (larger indices to smaller)
12 for (int i = n - 1; i >= 0; i--) {
13 // Base case: single element
14 dp[i][i] = nums[i];
15 maxXor[i][i] = nums[i];
16
17 // Fill the table for subarrays of increasing length
18 for (int j = i + 1; j < n; j++) {
19 // XOR pyramid formula: current value is XOR of two adjacent pyramid values
20 dp[i][j] = dp[i][j - 1] ^ dp[i + 1][j];
21
22 // Maximum XOR is either:
23 // 1. Current pyramid value
24 // 2. Maximum from left subarray (excluding rightmost element)
25 // 3. Maximum from right subarray (excluding leftmost element)
26 maxXor[i][j] = Math.max(dp[i][j],
27 Math.max(maxXor[i][j - 1], maxXor[i + 1][j]));
28 }
29 }
30
31 // Process each query
32 int queryCount = queries.length;
33 int[] result = new int[queryCount];
34
35 for (int i = 0; i < queryCount; i++) {
36 int left = queries[i][0];
37 int right = queries[i][1];
38
39 // Get the maximum XOR value for the queried range
40 result[i] = maxXor[left][right];
41 }
42
43 return result;
44 }
45}
46
1class Solution {
2public:
3 vector<int> maximumSubarrayXor(vector<int>& nums, vector<vector<int>>& queries) {
4 int n = nums.size();
5
6 // dp[i][j] stores the XOR value for the subarray from index i to j
7 // This follows a special XOR pattern where adjacent subarrays are XORed recursively
8 vector<vector<int>> dp(n, vector<int>(n));
9
10 // maxXor[i][j] stores the maximum XOR value that can be obtained
11 // from any subarray within the range [i, j]
12 vector<vector<int>> maxXor(n, vector<int>(n));
13
14 // Build the DP table from bottom-right to top-left
15 // Process shorter subarrays before longer ones
16 for (int i = n - 1; i >= 0; --i) {
17 // Base case: single element subarray
18 dp[i][i] = nums[i];
19 maxXor[i][i] = nums[i];
20
21 // Extend the subarray from position i to j
22 for (int j = i + 1; j < n; ++j) {
23 // Calculate XOR value for subarray [i, j]
24 // The pattern is: dp[i][j] = dp[i][j-1] XOR dp[i+1][j]
25 // This creates a pyramid-like XOR computation
26 dp[i][j] = dp[i][j - 1] ^ dp[i + 1][j];
27
28 // Track the maximum XOR value in range [i, j]
29 // It can be either:
30 // 1. The current computed XOR value dp[i][j]
31 // 2. The maximum from subarray [i, j-1]
32 // 3. The maximum from subarray [i+1, j]
33 maxXor[i][j] = max({dp[i][j], maxXor[i][j - 1], maxXor[i + 1][j]});
34 }
35 }
36
37 // Process each query and collect results
38 vector<int> result;
39 for (const auto& query : queries) {
40 int left = query[0];
41 int right = query[1];
42
43 // Add the precomputed maximum XOR value for the queried range
44 result.push_back(maxXor[left][right]);
45 }
46
47 return result;
48 }
49};
50
1/**
2 * Finds the maximum XOR value for subarrays within given query ranges
3 * @param nums - The input array of numbers
4 * @param queries - Array of query ranges, each containing [left, right] indices
5 * @returns Array of maximum XOR values for each query range
6 */
7function maximumSubarrayXor(nums: number[], queries: number[][]): number[] {
8 const arrayLength: number = nums.length;
9
10 // Dynamic programming table for XOR values
11 // xorTable[i][j] represents XOR of all elements in the subarray from i to j
12 const xorTable: number[][] = Array.from(
13 { length: arrayLength },
14 () => Array(arrayLength).fill(0)
15 );
16
17 // Dynamic programming table for maximum XOR values
18 // maxXorTable[i][j] represents the maximum XOR value in any subarray within [i, j]
19 const maxXorTable: number[][] = Array.from(
20 { length: arrayLength },
21 () => Array(arrayLength).fill(0)
22 );
23
24 // Build the DP tables from bottom-right to top-left
25 for (let startIndex: number = arrayLength - 1; startIndex >= 0; startIndex--) {
26 // Initialize diagonal elements (single element subarrays)
27 xorTable[startIndex][startIndex] = nums[startIndex];
28 maxXorTable[startIndex][startIndex] = nums[startIndex];
29
30 // Fill the tables for subarrays of increasing length
31 for (let endIndex: number = startIndex + 1; endIndex < arrayLength; endIndex++) {
32 // Calculate XOR value using previously computed values
33 // Based on the property: XOR of range [i,j] can be computed from [i,j-1] and [i+1,j]
34 xorTable[startIndex][endIndex] =
35 xorTable[startIndex][endIndex - 1] ^ xorTable[startIndex + 1][endIndex];
36
37 // Update maximum XOR value by considering:
38 // 1. Current XOR value
39 // 2. Maximum from the left subrange [i, j-1]
40 // 3. Maximum from the bottom subrange [i+1, j]
41 maxXorTable[startIndex][endIndex] = Math.max(
42 xorTable[startIndex][endIndex],
43 Math.max(
44 maxXorTable[startIndex][endIndex - 1],
45 maxXorTable[startIndex + 1][endIndex]
46 )
47 );
48 }
49 }
50
51 // Process each query and return the maximum XOR value for the specified range
52 return queries.map(([leftIndex, rightIndex]: number[]) => maxXorTable[leftIndex][rightIndex]);
53}
54
Time and Space Complexity
The time complexity is O(n^2 + m)
, where n
is the length of the array nums
and m
is the length of the array queries
.
- The nested loops iterate from
i = n-1
down to0
and for eachi
,j
goes fromi+1
ton-1
. This creates approximatelyn^2/2
iterations, which isO(n^2)
. - Inside the inner loop, each operation (XOR and max comparisons) takes
O(1)
time. - The list comprehension at the end processes
m
queries, each takingO(1)
time to access the precomputed value ing[l][r]
, resulting inO(m)
time. - Total time complexity:
O(n^2) + O(m) = O(n^2 + m)
.
The space complexity is O(n^2)
.
- Two 2D arrays
f
andg
are created, each of sizen × n
, requiringO(n^2)
space each. - The output list stores
m
values, requiringO(m)
space, but this is typically not counted as auxiliary space since it's the required output. - Total space complexity:
O(n^2) + O(n^2) = O(n^2)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect DP Table Iteration Order
One of the most common mistakes is iterating through the DP tables in the wrong order. The algorithm requires building the solution from smaller subarrays to larger ones, but the dependency pattern is not immediately obvious.
Wrong approach:
# This won't work - iterating in the wrong order
for i in range(n):
for j in range(i, n):
if i == j:
dp_xor[i][j] = nums[i]
else:
dp_xor[i][j] = dp_xor[i][j-1] ^ dp_xor[i+1][j] # i+1 might not be computed yet!
Why it fails: When computing dp_xor[i][j]
, we need both dp_xor[i][j-1]
and dp_xor[i+1][j]
to be already computed. If we iterate with i
from 0 to n-1, dp_xor[i+1][j]
won't be available yet.
Correct approach:
# Iterate from bottom-right to top-left
for i in range(n - 1, -1, -1): # Start from the last row, move upward
for j in range(i, n): # For each row, move left to right
# Now dp_xor[i+1][j] is guaranteed to be computed
2. Confusing XOR Score Calculation with Simple XOR
Another pitfall is misunderstanding what the "XOR score" means. It's not just XORing all elements in the subarray.
Wrong interpretation:
# This is NOT the XOR score - it's just a simple XOR of all elements
xor_score = nums[i]
for k in range(i + 1, j + 1):
xor_score ^= nums[k]
Correct understanding: The XOR score is the result of a triangular reduction process where we repeatedly XOR adjacent elements and reduce the array size until one element remains. This forms a pattern similar to Pascal's triangle but with XOR operations.
3. Not Handling Base Cases Properly
Forgetting to initialize single-element subarrays can lead to incorrect results.
Missing base case:
for i in range(n - 1, -1, -1):
for j in range(i + 1, n): # Skips the case when i == j
dp_xor[i][j] = dp_xor[i][j-1] ^ dp_xor[i+1][j]
Correct initialization:
for i in range(n - 1, -1, -1):
dp_xor[i][i] = nums[i] # Initialize base case first
max_xor[i][i] = nums[i]
for j in range(i + 1, n): # Then handle larger subarrays
# ...
4. Incorrect Maximum Calculation
When computing max_xor[i][j]
, forgetting to consider all three possibilities can lead to wrong answers.
Incomplete maximum calculation:
# Only considering current XOR score and left subrange
max_xor[i][j] = max(dp_xor[i][j], max_xor[i][j-1])
# Missing max_xor[i+1][j]!
Complete solution:
max_xor[i][j] = max(
dp_xor[i][j], # Current subarray's XOR score
max_xor[i][j-1], # Maximum in left subrange
max_xor[i+1][j] # Maximum in right subrange
)
This ensures we consider all possible subarrays within the range [i, j].
What's the output of running the following function using the following tree as input?
1def serialize(root):
2 res = []
3 def dfs(root):
4 if not root:
5 res.append('x')
6 return
7 res.append(root.val)
8 dfs(root.left)
9 dfs(root.right)
10 dfs(root)
11 return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4 StringJoiner res = new StringJoiner(" ");
5 serializeDFS(root, res);
6 return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10 if (root == null) {
11 result.add("x");
12 return;
13 }
14 result.add(Integer.toString(root.val));
15 serializeDFS(root.left, result);
16 serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2 let res = [];
3 serialize_dfs(root, res);
4 return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8 if (!root) {
9 res.push("x");
10 return;
11 }
12 res.push(root.val);
13 serialize_dfs(root.left, res);
14 serialize_dfs(root.right, res);
15}
16
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!