Facebook Pixel

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:

  1. Start with an array a
  2. Repeatedly apply these operations until only one element remains:
    • For all indices i except the last one, simultaneously replace a[i] with a[i] XOR a[i + 1]
    • Remove the last element of the array
  3. 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:

  1. Consider all possible subarrays within nums[l..r]
  2. Calculate the XOR score for each subarray
  3. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 index i to j-1
  • f[i+1][j] represents the result after applying the XOR operations on elements from index i+1 to j
  • 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:

  1. The XOR score of the current range itself: f[i][j]
  2. The maximum from the range excluding the last element: g[i][j-1]
  3. 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:

  1. Table f[i][j]: Stores the XOR score of the subarray nums[i..j]
  2. Table g[i][j]: Stores the maximum XOR score among all subarrays within nums[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]

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 Evaluator

Example 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 j
  • g[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 to 0 and for each i, j goes from i+1 to n-1. This creates approximately n^2/2 iterations, which is O(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 taking O(1) time to access the precomputed value in g[l][r], resulting in O(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 and g are created, each of size n × n, requiring O(n^2) space each.
  • The output list stores m values, requiring O(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].

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More