1879. Minimum XOR Sum of Two Arrays
Problem Description
You have two integer arrays nums1
and nums2
, both of length n
.
The XOR sum is calculated by pairing each element from nums1
with exactly one element from nums2
and computing: (nums1[0] XOR nums2[0]) + (nums1[1] XOR nums2[1]) + ... + (nums1[n-1] XOR nums2[n-1])
.
Your task is to rearrange the elements of nums2
to create a new pairing that produces the minimum possible XOR sum.
For example, given nums1 = [1,2,3]
and nums2 = [3,2,1]
:
- The current pairing gives:
(1 XOR 3) + (2 XOR 2) + (3 XOR 1) = 2 + 0 + 2 = 4
- But you could rearrange
nums2
to find a better pairing that gives a smaller sum
The solution uses dynamic programming with bitmask to track which elements from nums2
have been used. The state f[i][j]
represents the minimum XOR sum when considering the first i
elements of nums1
and the set of used elements from nums2
represented by bitmask j
. For each element in nums1
, we try pairing it with each unused element in nums2
and track the minimum sum.
The final answer is stored in f[-1][-1]
, which represents using all elements from both arrays.
Intuition
The key insight is that this is an assignment problem - we need to find the optimal one-to-one pairing between elements of nums1
and nums2
to minimize the total XOR sum.
Since we need to pair each element in nums1
with exactly one element in nums2
, and each element in nums2
can only be used once, this is essentially finding the best permutation of nums2
to match with the fixed order of nums1
.
A brute force approach would try all n!
permutations of nums2
, which becomes impractical for larger arrays. Instead, we can think of this as a step-by-step decision process: for each element in nums1
, we choose which available element from nums2
to pair it with.
This naturally leads to dynamic programming with bitmask. The bitmask helps us track which elements from nums2
have already been used in previous pairings. For a bitmask j
, if the k-th
bit is set to 1, it means nums2[k]
has been used.
The recurrence relation emerges from considering: if we're at position i
in nums1
and have already used certain elements from nums2
(represented by bitmask j
), what's the minimum XOR sum we can achieve? We try pairing nums1[i-1]
with each unused element from nums2
and take the minimum.
The state transition is: f[i][j] = min(f[i-1][j without k-th bit] + (nums1[i-1] XOR nums2[k]))
for all k
where the k-th
bit in j
is set.
This approach efficiently explores all valid pairings while avoiding redundant calculations through memoization, reducing the complexity from O(n!)
to O(n² × 2ⁿ)
.
Learn more about Dynamic Programming and Bitmask patterns.
Solution Approach
The implementation uses dynamic programming with bitmask to solve this assignment problem efficiently.
Data Structure Setup:
- Create a 2D DP table
f
with dimensions(n+1) × (1 << n)
wheren
is the length of the arrays f[i][j]
represents the minimum XOR sum when we've processed the firsti
elements ofnums1
and used the elements fromnums2
indicated by bitmaskj
- Initialize all values to infinity (
inf
) exceptf[0][0] = 0
(base case: no elements processed, no elements used)
Algorithm Implementation:
-
Outer Loop - Process each element in nums1:
for i, x in enumerate(nums1, 1):
- Iterate through
nums1
with 1-based indexing for the DP table x
is the current element fromnums1
to be paired
- Iterate through
-
Middle Loop - Iterate through all possible bitmasks:
for j in range(1 << n):
- Each bitmask
j
represents a subset of used elements fromnums2
- For
n
elements, there are2^n
possible subsets
- Each bitmask
-
Inner Loop - Try pairing with each used element:
for k in range(n): if j >> k & 1:
- Check if the
k-th
bit is set in bitmaskj
using bit manipulationj >> k & 1
- If set, it means
nums2[k]
is part of the current subset being considered
- Check if the
-
State Transition:
f[i][j] = min(f[i][j], f[i - 1][j ^ (1 << k)] + (x ^ nums2[k]))
j ^ (1 << k)
removes thek-th
bit fromj
, representing the state before usingnums2[k]
- Add the XOR value
(x ^ nums2[k])
to the previous state's minimum sum - Take the minimum across all possible pairings for position
i
-
Return the Result:
return f[-1][-1]
f[-1][-1]
orf[n][(1 << n) - 1]
contains the minimum XOR sum when alln
elements fromnums1
are processed and all elements fromnums2
are used- The bitmask
(1 << n) - 1
has alln
bits set, representing that all elements are used
The algorithm systematically builds up the solution by considering all valid ways to assign elements from nums2
to elements in nums1
, ensuring each element is used exactly once while tracking the minimum possible XOR sum.
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 trace through a small example with nums1 = [1, 2]
and nums2 = [3, 4]
.
Setup:
- n = 2
- Create DP table
f
with dimensions 3 × 4 (since we have 2 elements and 2^2 = 4 possible bitmasks) - Initialize
f[0][0] = 0
, all others to infinity
Bitmask representations:
00
(0): no elements from nums2 used01
(1): nums2[0] = 3 used10
(2): nums2[1] = 4 used11
(3): both nums2[0] and nums2[1] used
Step 1: Process nums1[0] = 1 (i = 1)
For bitmask 01
(j = 1, using nums2[0] = 3):
- k = 0: bit is set, so we can pair nums1[0] with nums2[0]
- Previous state:
f[0][0]
(no elements used before) f[1][1] = f[0][0] + (1 XOR 3) = 0 + 2 = 2
For bitmask 10
(j = 2, using nums2[1] = 4):
- k = 1: bit is set, so we can pair nums1[0] with nums2[1]
- Previous state:
f[0][0]
f[1][2] = f[0][0] + (1 XOR 4) = 0 + 5 = 5
For bitmask 11
(j = 3, using both elements):
- k = 0:
f[1][3] = min(inf, f[0][2] + (1 XOR 3))
= inf (f[0][2] is inf) - k = 1:
f[1][3] = min(inf, f[0][1] + (1 XOR 4))
= inf (f[0][1] is inf)
Step 2: Process nums1[1] = 2 (i = 2)
For bitmask 11
(j = 3, both elements used):
- k = 0: bit is set
- Previous state:
f[1][2]
(had used nums2[1], now adding nums2[0]) f[2][3] = min(inf, f[1][2] + (2 XOR 3)) = 5 + 1 = 6
- Previous state:
- k = 1: bit is set
- Previous state:
f[1][1]
(had used nums2[0], now adding nums2[1]) f[2][3] = min(6, f[1][1] + (2 XOR 4)) = min(6, 2 + 6) = 6
- Previous state:
Result:
f[2][3] = 6
, which represents the minimum XOR sum when all elements are paired.
This corresponds to:
- Pairing nums1[0]=1 with nums2[0]=3: 1 XOR 3 = 2
- Pairing nums1[1]=2 with nums2[1]=4: 2 XOR 4 = 6
- Total: 2 + 6 = 8
Or alternatively:
- Pairing nums1[0]=1 with nums2[1]=4: 1 XOR 4 = 5
- Pairing nums1[1]=2 with nums2[0]=3: 2 XOR 3 = 1
- Total: 5 + 1 = 6 (minimum)
The algorithm correctly finds that rearranging nums2 to [4, 3] gives the minimum XOR sum of 6.
Solution Implementation
1class Solution:
2 def minimumXORSum(self, nums1: List[int], nums2: List[int]) -> int:
3 n = len(nums2)
4
5 # dp[i][mask] represents the minimum XOR sum when considering
6 # the first i elements from nums1 and using elements from nums2
7 # indicated by the bitmask
8 dp = [[float('inf')] * (1 << n) for _ in range(n + 1)]
9
10 # Base case: no elements from nums1, no elements from nums2 used
11 dp[0][0] = 0
12
13 # Iterate through each element in nums1
14 for i, num1_element in enumerate(nums1, 1):
15 # Try all possible masks (subsets of nums2)
16 for mask in range(1 << n):
17 # Try pairing current nums1[i-1] with each available element in nums2
18 for k in range(n):
19 # Check if k-th element of nums2 is used in current mask
20 if (mask >> k) & 1:
21 # Previous mask without k-th element
22 prev_mask = mask ^ (1 << k)
23 # Calculate XOR of current pair
24 xor_value = num1_element ^ nums2[k]
25 # Update minimum XOR sum
26 dp[i][mask] = min(dp[i][mask],
27 dp[i - 1][prev_mask] + xor_value)
28
29 # Return minimum XOR sum using all elements from both arrays
30 # Last row, last column (all bits set in mask)
31 return dp[n][(1 << n) - 1]
32
1class Solution {
2 public int minimumXORSum(int[] nums1, int[] nums2) {
3 int n = nums1.length;
4
5 // dp[i][mask] represents the minimum XOR sum when pairing
6 // the first i elements from nums1 with elements from nums2
7 // where 'mask' indicates which elements from nums2 have been used
8 int[][] dp = new int[n + 1][1 << n];
9
10 // Initialize all states with a large value (representing infinity)
11 for (int[] row : dp) {
12 Arrays.fill(row, 1 << 30);
13 }
14
15 // Base case: no elements paired, sum is 0
16 dp[0][0] = 0;
17
18 // Iterate through each element in nums1
19 for (int i = 1; i <= n; i++) {
20 // Try all possible masks (states of nums2 usage)
21 for (int mask = 0; mask < (1 << n); mask++) {
22 // Try pairing nums1[i-1] with each available element in nums2
23 for (int j = 0; j < n; j++) {
24 // Check if j-th element of nums2 is used in current mask
25 if ((mask >> j & 1) == 1) {
26 // Previous mask without j-th element
27 int previousMask = mask ^ (1 << j);
28 // Calculate XOR of current pair
29 int xorValue = nums1[i - 1] ^ nums2[j];
30 // Update minimum sum for current state
31 dp[i][mask] = Math.min(dp[i][mask],
32 dp[i - 1][previousMask] + xorValue);
33 }
34 }
35 }
36 }
37
38 // Return minimum sum when all n elements from nums1 are paired
39 // with all n elements from nums2 (all bits set in mask)
40 return dp[n][(1 << n) - 1];
41 }
42}
43
1class Solution {
2public:
3 int minimumXORSum(vector<int>& nums1, vector<int>& nums2) {
4 int n = nums1.size();
5
6 // dp[i][mask] represents the minimum XOR sum when considering
7 // the first i elements from nums1 and the elements from nums2
8 // indicated by the bitmask
9 int dp[n + 1][1 << n];
10
11 // Initialize all values to a large number (infinity)
12 memset(dp, 0x3f, sizeof(dp));
13
14 // Base case: no elements selected, XOR sum is 0
15 dp[0][0] = 0;
16
17 // Iterate through each element in nums1
18 for (int i = 1; i <= n; ++i) {
19 // Iterate through all possible bitmasks
20 for (int mask = 0; mask < (1 << n); ++mask) {
21 // Try pairing nums1[i-1] with each available element in nums2
22 for (int j = 0; j < n; ++j) {
23 // Check if j-th element of nums2 is selected in current mask
24 if ((mask >> j) & 1) {
25 // Previous mask without j-th element
26 int prevMask = mask ^ (1 << j);
27
28 // Update minimum XOR sum by pairing nums1[i-1] with nums2[j]
29 dp[i][mask] = min(dp[i][mask],
30 dp[i - 1][prevMask] + (nums1[i - 1] ^ nums2[j]));
31 }
32 }
33 }
34 }
35
36 // Return the minimum XOR sum when all n elements from both arrays are paired
37 // (1 << n) - 1 represents the bitmask with all n bits set to 1
38 return dp[n][(1 << n) - 1];
39 }
40};
41
1/**
2 * Finds the minimum XOR sum by pairing elements from two arrays
3 * Uses dynamic programming with bitmask to track which elements from nums2 have been used
4 *
5 * @param nums1 - First array of numbers
6 * @param nums2 - Second array of numbers (same length as nums1)
7 * @returns The minimum possible XOR sum after pairing all elements
8 */
9function minimumXORSum(nums1: number[], nums2: number[]): number {
10 const arrayLength: number = nums1.length;
11
12 // dp[i][mask] represents the minimum XOR sum when considering first i elements from nums1
13 // and using elements from nums2 according to the bitmask
14 const dp: number[][] = Array(arrayLength + 1)
15 .fill(0)
16 .map(() => Array(1 << arrayLength).fill(1 << 30)); // Initialize with large value
17
18 // Base case: no elements selected, sum is 0
19 dp[0][0] = 0;
20
21 // Iterate through each element in nums1
22 for (let elementIndex = 1; elementIndex <= arrayLength; ++elementIndex) {
23 // Iterate through all possible bitmask states
24 for (let bitmask = 0; bitmask < (1 << arrayLength); ++bitmask) {
25 // Try pairing current nums1 element with each available nums2 element
26 for (let nums2Index = 0; nums2Index < arrayLength; ++nums2Index) {
27 // Check if nums2[nums2Index] is used in current bitmask
28 if (((bitmask >> nums2Index) & 1) === 1) {
29 // Previous state: remove current nums2 element from bitmask
30 const previousMask: number = bitmask ^ (1 << nums2Index);
31
32 // Calculate XOR value for current pairing
33 const currentXorValue: number = nums1[elementIndex - 1] ^ nums2[nums2Index];
34
35 // Update minimum XOR sum for current state
36 dp[elementIndex][bitmask] = Math.min(
37 dp[elementIndex][bitmask],
38 dp[elementIndex - 1][previousMask] + currentXorValue
39 );
40 }
41 }
42 }
43 }
44
45 // Return minimum XOR sum when all elements are paired (all bits set in mask)
46 return dp[arrayLength][(1 << arrayLength) - 1];
47}
48
Time and Space Complexity
Time Complexity: O(n² * 2^n)
The algorithm uses dynamic programming with bitmask. Breaking down the nested loops:
- The outer loop iterates through
nums1
, which runsn
times - The middle loop iterates through all possible bitmask states, which is
2^n
iterations - The inner loop iterates through each bit position to check which elements of
nums2
are used, which runsn
times
Therefore, the total time complexity is O(n * 2^n * n) = O(n² * 2^n)
Space Complexity: O(n * 2^n)
The space is dominated by the 2D DP array f
:
- The first dimension has size
n + 1
(for each element innums1
plus initial state) - The second dimension has size
2^n
(for all possible bitmask states representing which elements ofnums2
have been used)
Thus, the space complexity is O((n + 1) * 2^n) = O(n * 2^n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Bitmask Interpretation
A common mistake is misunderstanding what the bitmask represents. Developers often confuse whether bit i
being set means element i
has been used or is available to use.
Wrong interpretation:
# Incorrectly checking if element is available (not used) if not (mask >> k) & 1: # Wrong! prev_mask = mask | (1 << k) # Adding instead of removing
Correct approach:
# Bit set means element IS used in current state if (mask >> k) & 1: prev_mask = mask ^ (1 << k) # Remove k-th bit to get previous state
2. Inefficient State Iteration Order
Processing states in the wrong order can lead to accessing uninitialized values or incorrect results.
Problematic approach:
# Wrong: iterating through masks that don't match the number of elements processed
for i in range(1, n + 1):
for mask in range(1 << n):
if bin(mask).count('1') != i: # Unnecessary check, wastes time
continue
Optimized solution:
# Better: only iterate through masks with exactly i bits set
for i in range(1, n + 1):
for mask in range(1 << n):
if bin(mask).count('1') == i: # Only process relevant masks
# Process state
3. Memory Overflow with Large Arrays
The space complexity is O(n × 2^n), which becomes problematic for large n (typically n > 20).
Memory-intensive approach:
dp = [[float('inf')] * (1 << n) for _ in range(n + 1)] # Uses O(n × 2^n) space
Space-optimized solution using rolling array:
# Only keep current and previous row
prev = [float('inf')] * (1 << n)
prev[0] = 0
for i, num1_element in enumerate(nums1):
curr = [float('inf')] * (1 << n)
for mask in range(1 << n):
if bin(mask).count('1') == i + 1:
for k in range(n):
if (mask >> k) & 1:
prev_mask = mask ^ (1 << k)
curr[mask] = min(curr[mask],
prev[prev_mask] + (num1_element ^ nums2[k]))
prev = curr
return prev[(1 << n) - 1]
4. Bit Operation Precedence Errors
Forgetting operator precedence can lead to incorrect bit manipulation.
Common mistake:
# Wrong: & has lower precedence than >> if mask >> k & 1: # This works but can be confusing # Clearer with parentheses: if (mask >> k) & 1: # More explicit about operation order # Another common error: prev_mask = mask - 1 << k # Wrong! Subtraction happens before shift prev_mask = mask ^ (1 << k) # Correct: use XOR to toggle bit
5. Off-by-One Errors in Indexing
Mixing 0-based and 1-based indexing can cause subtle bugs.
Error-prone code:
for i in range(n): # 0-based
for mask in range(1 << n):
# Accessing dp[i+1] but nums1[i]
dp[i+1][mask] = min(dp[i+1][mask],
dp[i][prev_mask] + (nums1[i+1] ^ nums2[k])) # Wrong index!
Consistent indexing:
# Use enumerate with start=1 for clarity
for i, num1_element in enumerate(nums1, 1):
# Now i matches dp table indexing
# num1_element is the actual value, no index confusion
dp[i][mask] = min(dp[i][mask],
dp[i-1][prev_mask] + (num1_element ^ nums2[k]))
How would you design a stack which has a function min
that returns the minimum element in the stack, in addition to push
and pop
? All push
, pop
, min
should have running time O(1)
.
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
Bitmask and Dynamic Programming Bit manipulation is a crucial aspect of computer programming and one of the most powerful tools for bit manipulation is bitmasks Let's first understand what a bit is A bit is a binary digit It's the smallest piece of data in a computer and can be
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
Want a Structured Path to Master System Design Too? Don’t Miss This!