Facebook Pixel

3068. Find the Maximum Sum of Node Values

Problem Description

You have an undirected tree with n nodes numbered from 0 to n - 1. The tree structure is defined by an array edges of length n - 1, where each edges[i] = [ui, vi] represents an edge between nodes ui and vi. You're also given:

  • A positive integer k
  • An array nums of length n, where nums[i] is the value of node i

Alice can perform the following operation any number of times (including zero):

  1. Choose any edge [u, v] in the tree
  2. Update the values of both connected nodes:
    • nums[u] = nums[u] XOR k
    • nums[v] = nums[v] XOR k

The goal is to find the maximum possible sum of all node values after performing any number of operations.

Key Insight: When you perform an operation on an edge, both endpoints get XORed with k. This means that in any sequence of operations, the XOR with k will be applied to an even number of nodes total (since each operation affects exactly 2 nodes).

The problem essentially asks: given that you can XOR any even number of nodes with k, what's the maximum sum you can achieve?

For each node, you have two choices:

  • Keep its original value nums[i]
  • Change it to nums[i] XOR k

The constraint is that the total number of nodes that get XORed must be even.

The solution uses dynamic programming where:

  • f0 tracks the maximum sum when an even number of nodes have been XORed
  • f1 tracks the maximum sum when an odd number of nodes have been XORed

Since we must end with an even count, the answer is f0 after processing all nodes.

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

Intuition

The first key observation is understanding what happens when we perform operations on the tree. When we select an edge [u, v] and apply the XOR operation, both nodes u and v get XORed with k. If we think about any path in the tree and apply operations to all edges on that path, intermediate nodes will be XORed twice (once for each adjacent edge), while only the endpoints are XORed once.

Since x XOR k XOR k = x (XORing twice cancels out), the intermediate nodes return to their original values. This means we can effectively "transfer" the XOR operation between any two nodes in the tree, as long as we apply it to both.

The crucial insight is that every operation affects exactly 2 nodes, so the total number of nodes that end up being XORed with k must always be even. This transforms our tree problem into a simpler array problem: select an even number of elements from nums to XOR with k to maximize the sum.

Now we need to decide for each node whether to XOR it or not, with the constraint that the total count must be even. This naturally leads to a dynamic programming approach where we track two states:

  • The maximum sum when we've XORed an even number of elements so far
  • The maximum sum when we've XORed an odd number of elements so far

As we process each element, we have two choices:

  1. Don't XOR the current element (keep it as is)
  2. XOR the current element with k

Each choice affects whether our count remains even/odd or flips. If we've XORed an even number so far and XOR the current element, we'll have an odd count. If we've XORed an odd number and XOR the current element, we'll have an even count.

This parity tracking with maximization leads directly to the two-state DP solution where f0 and f1 represent our maximum sums for even and odd counts respectively.

Learn more about Greedy, Tree, Dynamic Programming and Sorting patterns.

Solution Approach

We implement a dynamic programming solution with two states to track the maximum sum based on the parity of XORed elements.

State Definition:

  • f0: Maximum sum when an even number of elements have been XORed with k
  • f1: Maximum sum when an odd number of elements have been XORed with k

Initialization:

  • f0 = 0: Initially, zero elements are XORed (even count), so the sum is 0
  • f1 = -inf: Initially, it's impossible to have an odd count of XORed elements, so we use negative infinity

State Transition: For each element x in nums, we have two choices:

  1. Keep x unchanged:

    • If we had an even count (f0), adding unchanged x keeps it even: contributes to new f0
    • If we had an odd count (f1), adding unchanged x keeps it odd: contributes to new f1
  2. XOR x with k:

    • If we had an even count (f0), XORing x makes it odd: contributes to new f1
    • If we had an odd count (f1), XORing x makes it even: contributes to new f0

The state transition equations become:

new_f0 = max(f0 + x, f1 + (x ^ k))
new_f1 = max(f1 + x, f0 + (x ^ k))

Implementation:

f0, f1 = 0, -inf
for x in nums:
    f0, f1 = max(f0 + x, f1 + (x ^ k)), max(f1 + x, f0 + (x ^ k))
return f0

We iterate through each element once, updating both states simultaneously using parallel assignment. The parallel assignment ensures we use the old values of f0 and f1 for both calculations before updating them.

Final Result: We return f0 because we need an even number of elements to be XORed (as operations always affect pairs of nodes).

Time Complexity: O(n) - single pass through the array Space Complexity: O(1) - only two variables for state tracking

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 a small example to illustrate the solution approach.

Example: nums = [2, 3, 1], k = 6

For each element, we can either keep it or XOR it with 6:

  • Node 0: Keep value 2, or change to 2 XOR 6 = 4
  • Node 1: Keep value 3, or change to 3 XOR 6 = 5
  • Node 2: Keep value 1, or change to 1 XOR 6 = 7

Since we must XOR an even number of nodes (0, 2, or more nodes), let's trace through our DP:

Initial State:

  • f0 = 0 (even count of XORed nodes, sum = 0)
  • f1 = -inf (odd count impossible initially)

Processing nums[0] = 2:

  • Keep 2 unchanged:
    • From f0 (even): 0 + 2 = 2 (stays even)
    • From f1 (odd): -inf + 2 = -inf (stays odd)
  • XOR with k (2 XOR 6 = 4):
    • From f0 (even): 0 + 4 = 4 (becomes odd)
    • From f1 (odd): -inf + 4 = -inf (becomes even)

New states:

  • f0 = max(2, -inf) = 2 (best even: don't XOR node 0)
  • f1 = max(-inf, 4) = 4 (best odd: XOR node 0)

Processing nums[1] = 3:

  • Keep 3 unchanged:
    • From f0: 2 + 3 = 5 (stays even)
    • From f1: 4 + 3 = 7 (stays odd)
  • XOR with k (3 XOR 6 = 5):
    • From f0: 2 + 5 = 7 (becomes odd)
    • From f1: 4 + 5 = 9 (becomes even)

New states:

  • f0 = max(5, 9) = 9 (best even: XOR nodes 0 and 1)
  • f1 = max(7, 7) = 7 (best odd: either XOR only node 0 or only node 1)

Processing nums[2] = 1:

  • Keep 1 unchanged:
    • From f0: 9 + 1 = 10 (stays even)
    • From f1: 7 + 1 = 8 (stays odd)
  • XOR with k (1 XOR 6 = 7):
    • From f0: 9 + 7 = 16 (becomes odd)
    • From f1: 7 + 7 = 14 (becomes even)

Final states:

  • f0 = max(10, 14) = 14 (best even: XOR all three nodes, then one more)
  • f1 = max(8, 16) = 16 (best odd: XOR nodes 0, 1, and 2)

Result: Return f0 = 14

The optimal solution XORs nodes 0 and 2 (even count):

  • Node 0: 2 → 4 (XORed)
  • Node 1: 3 → 3 (unchanged)
  • Node 2: 1 → 7 (XORed)
  • Sum: 4 + 3 + 7 = 14

Solution Implementation

1class Solution:
2    def maximumValueSum(self, nums: List[int], k: int, edges: List[List[int]]) -> int:
3        # Dynamic programming approach
4        # even_count: max sum when we've XORed an even number of nodes
5        # odd_count: max sum when we've XORed an odd number of nodes
6        even_count, odd_count = 0, float('-inf')
7      
8        # Process each node value
9        for value in nums:
10            # Calculate new states after considering current node
11            # Option 1: Keep current node unchanged
12            # Option 2: XOR current node with k
13            new_even = max(
14                even_count + value,           # Don't XOR: even stays even
15                odd_count + (value ^ k)        # XOR: odd becomes even
16            )
17            new_odd = max(
18                odd_count + value,             # Don't XOR: odd stays odd
19                even_count + (value ^ k)       # XOR: even becomes odd
20            )
21          
22            # Update states for next iteration
23            even_count, odd_count = new_even, new_odd
24      
25        # Return maximum sum with even number of XOR operations
26        # (valid configuration requires even number of operations)
27        return even_count
28
1class Solution {
2    public long maximumValueSum(int[] nums, int k, int[][] edges) {
3        // Initialize dynamic programming states
4        // maxSumEvenXORs: Maximum sum when even number of XOR operations applied
5        // maxSumOddXORs: Maximum sum when odd number of XOR operations applied
6        long maxSumEvenXORs = 0;
7        long maxSumOddXORs = Long.MIN_VALUE; // Start with negative infinity for invalid state
8      
9        // Process each node value
10        for (int nodeValue : nums) {
11            // Store previous even state for transition calculation
12            long previousEvenState = maxSumEvenXORs;
13          
14            // Update even state: can come from:
15            // 1. Previous even state + current value (no XOR on current)
16            // 2. Previous odd state + XORed current value (XOR on current)
17            maxSumEvenXORs = Math.max(
18                maxSumEvenXORs + nodeValue,           // Don't XOR current node
19                maxSumOddXORs + (nodeValue ^ k)       // XOR current node
20            );
21          
22            // Update odd state: can come from:
23            // 1. Previous odd state + current value (no XOR on current)
24            // 2. Previous even state + XORed current value (XOR on current)
25            maxSumOddXORs = Math.max(
26                maxSumOddXORs + nodeValue,            // Don't XOR current node
27                previousEvenState + (nodeValue ^ k)   // XOR current node
28            );
29        }
30      
31        // Return the maximum sum with even number of XOR operations
32        // (including zero XOR operations)
33        return maxSumEvenXORs;
34    }
35}
36
1class Solution {
2public:
3    long long maximumValueSum(vector<int>& nums, int k, vector<vector<int>>& edges) {
4        // Initialize DP states
5        // dpEvenXorCount: Maximum sum with even number of XOR operations applied
6        // dpOddXorCount: Maximum sum with odd number of XOR operations applied
7        long long dpEvenXorCount = 0;
8        long long dpOddXorCount = LLONG_MIN / 2;  // Use a large negative value to represent invalid state
9      
10        // Process each number in the array
11        for (int currentValue : nums) {
12            // Store the previous even count state for transition calculation
13            long long previousEvenCount = dpEvenXorCount;
14          
15            // Update dpEvenXorCount: We can reach even XOR count state by either:
16            // 1. Not XORing current value and maintaining even count (dpEvenXorCount + currentValue)
17            // 2. XORing current value when we had odd count (dpOddXorCount + (currentValue ^ k))
18            dpEvenXorCount = max(dpEvenXorCount + currentValue, 
19                                 dpOddXorCount + (currentValue ^ k));
20          
21            // Update dpOddXorCount: We can reach odd XOR count state by either:
22            // 1. Not XORing current value when we had odd count (dpOddXorCount + currentValue)
23            // 2. XORing current value when we had even count (previousEvenCount + (currentValue ^ k))
24            dpOddXorCount = max(dpOddXorCount + currentValue, 
25                               previousEvenCount + (currentValue ^ k));
26        }
27      
28        // Return the maximum sum with even number of XOR operations
29        // (valid final state since XOR operations must be applied in pairs along edges)
30        return dpEvenXorCount;
31    }
32};
33
1/**
2 * Calculates the maximum sum value of a tree after applying XOR operations
3 * @param nums - Array of node values
4 * @param k - XOR operand value
5 * @param edges - Array of edges representing tree connections (unused in optimization)
6 * @returns Maximum possible sum after operations
7 */
8function maximumValueSum(nums: number[], k: number, edges: number[][]): number {
9    // Dynamic programming states:
10    // evenXorCount: Maximum sum with even number of XOR operations applied
11    // oddXorCount: Maximum sum with odd number of XOR operations applied
12    let evenXorCount: number = 0;
13    let oddXorCount: number = -Infinity;
14  
15    // Process each node value
16    for (const nodeValue of nums) {
17        // Calculate next states:
18        // Option 1: Keep current value or apply XOR from odd state
19        const nextEvenXorCount: number = Math.max(
20            evenXorCount + nodeValue,           // Don't XOR current node (even stays even)
21            oddXorCount + (nodeValue ^ k)        // XOR current node (odd becomes even)
22        );
23      
24        // Option 2: Apply XOR from even state or keep value from odd state
25        const nextOddXorCount: number = Math.max(
26            oddXorCount + nodeValue,             // Don't XOR current node (odd stays odd)
27            evenXorCount + (nodeValue ^ k)       // XOR current node (even becomes odd)
28        );
29      
30        // Update states for next iteration
31        evenXorCount = nextEvenXorCount;
32        oddXorCount = nextOddXorCount;
33    }
34  
35    // Return maximum sum with even number of XOR operations
36    // (valid configuration requires even number of operations due to tree property)
37    return evenXorCount;
38}
39

Time and Space Complexity

The time complexity is O(n), where n is the length of the array nums. The algorithm iterates through the array exactly once, performing constant-time operations (addition, XOR operation, and max comparisons) for each element.

The space complexity is O(1). The algorithm only uses two variables f0 and f1 to track the state throughout the iteration, regardless of the input size. No additional data structures that scale with input size are created.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Misunderstanding the Tree Structure Constraint

Pitfall: Many developers initially think they need to traverse the tree structure using DFS/BFS since edges are provided. They might try to process nodes based on parent-child relationships or attempt to find connected components.

Why it happens: The problem provides edges and describes a tree structure, which naturally leads to thinking about graph traversal algorithms.

The Reality: The tree structure is actually irrelevant to the solution! The key insight is that each operation XORs exactly 2 nodes, so we just need to ensure an even total count of XORed nodes. The actual connectivity doesn't matter.

Solution: Focus on the constraint that operations always affect pairs of nodes, making the total count of XORed nodes even. Treat it as a simple array problem with a parity constraint.

2. Incorrect State Initialization

Pitfall: Initializing both states to 0:

even_count, odd_count = 0, 0  # WRONG!

Why it's wrong: Starting with odd_count = 0 implies we can have a valid sum of 0 with an odd number of XORed elements, which is impossible at the start (we haven't XORed anything yet).

Solution: Initialize odd_count to negative infinity:

even_count, odd_count = 0, float('-inf')

This ensures that any path requiring an odd count at intermediate steps is properly handled.

3. Sequential Assignment Instead of Parallel

Pitfall: Updating states sequentially:

# WRONG approach
even_count = max(even_count + value, odd_count + (value ^ k))
odd_count = max(odd_count + value, even_count + (value ^ k))  # Uses updated even_count!

Why it's wrong: The second assignment uses the already-updated even_count value instead of the original value from the previous iteration.

Solution: Use parallel assignment or temporary variables:

# Correct: Parallel assignment
even_count, odd_count = max(...), max(...)

# Alternative: Using temporary variables
new_even = max(even_count + value, odd_count + (value ^ k))
new_odd = max(odd_count + value, even_count + (value ^ k))
even_count, odd_count = new_even, new_odd

4. Returning the Wrong State

Pitfall: Returning max(even_count, odd_count) thinking we want the maximum possible sum regardless of parity.

Why it's wrong: The problem requires that the total number of XORed nodes must be even (since each operation affects exactly 2 nodes).

Solution: Always return even_count:

return even_count  # Must have even number of XORed nodes

5. Overthinking the XOR Operation

Pitfall: Trying to analyze when value ^ k > value for each node individually and making greedy decisions based on this comparison.

Why it's wrong: The constraint that we must XOR an even number of nodes means we can't just greedily pick all beneficial XORs. Sometimes we need to XOR a node that decreases its value to maintain the even count constraint while maximizing the total.

Solution: Let the DP handle all cases. The algorithm automatically considers both keeping and XORing each value while maintaining the parity constraint.

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

Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?


Recommended Readings

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

Load More