Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1707. Maximum XOR With an Element From Array - Leetcode Solution

Problem Description

You are given an integer array nums and a 2D array queries where each queries[i] = [x_i, m_i]. For each query, you need to find the maximum value of x_i ^ y where y is any element in nums such that y ≤ m_i. If no such y exists for a query, the answer should be -1.

Constraints:

  • Each query is independent.
  • You may not reuse elements between queries, but within a query, you may pick any y from nums as long as y ≤ m_i.
  • There may be multiple queries, and nums may be large.

Thought Process

The brute-force approach would be, for each query, to check every element in nums that is less than or equal to m_i, compute x_i ^ y, and keep track of the maximum. However, this is inefficient when both nums and queries are large.

To optimize, we need a way to quickly find, for each x_i and m_i, the number in nums not exceeding m_i that maximizes x_i ^ y. Since XOR is bitwise, we can use a Trie (prefix tree) to efficiently find the best candidate for each query.

The key insight is to process queries in order of increasing m_i, and build the Trie incrementally as more nums become eligible for each query.

Solution Approach

We use the following steps to solve the problem efficiently:

  1. Sort nums and queries:
    • Sort nums in ascending order.
    • Sort queries by m_i, keeping track of their original indices so we can return answers in the original order.
  2. Build a Trie:
    • The Trie is used to store the binary representation of numbers in nums. Each node represents a bit (0 or 1).
    • For each query, insert into the Trie all numbers from nums that are less than or equal to m_i (incrementally, as m_i increases).
  3. Process Queries:
    • For each query [x_i, m_i], if the Trie is empty (no eligible y), answer is -1.
    • Otherwise, traverse the Trie from the highest bit down, always trying to take the opposite bit of x_i at each level (to maximize XOR).
    • At the end, compute the XOR and store the answer at the corresponding original index.
  4. Return Results:
    • After all queries are processed, return the answers in the original query order.

This approach leverages the Trie for fast bitwise matching, and sorting to ensure we only insert necessary nums for each query.

Example Walkthrough

Example:
nums = [0,1,2,3,4]
queries = [[3,1],[1,3],[5,6]]
Let's walk through each query:

  1. Sort nums: [0,1,2,3,4] (already sorted)
    Sort queries by m_i: [[3,1],[1,3],[5,6]] (already sorted)
  2. Query 1: [3,1]
    • Eligible nums: [0,1] (≤1)
    • Insert 0, 1 into Trie.
    • For x=3, try to maximize 3 ^ y:
      • 3 ^ 0 = 3
      • 3 ^ 1 = 2
    • Maximum is 3.
  3. Query 2: [1,3]
    • Eligible nums: [0,1,2,3] (≤3)
    • Insert 2, 3 into Trie (since 0,1 already inserted).
    • For x=1:
      • 1 ^ 0 = 1
      • 1 ^ 1 = 0
      • 1 ^ 2 = 3
      • 1 ^ 3 = 2
    • Maximum is 3.
  4. Query 3: [5,6]
    • Eligible nums: [0,1,2,3,4] (≤6)
    • Insert 4 into Trie.
    • For x=5:
      • 5 ^ 0 = 5
      • 5 ^ 1 = 4
      • 5 ^ 2 = 7
      • 5 ^ 3 = 6
      • 5 ^ 4 = 1
    • Maximum is 7.

Final Output: [3,3,7]

Time and Space Complexity

  • Brute-force:
    • For each query, scan all nums (O(N)), so total time is O(QN).
  • Optimized (Trie):
    • Sorting nums: O(N log N)
    • Sorting queries: O(Q log Q)
    • Each number is inserted at most once into the Trie, each insert is O(32) (since integers are up to 32 bits), so O(N * 32).
    • Each query is processed in O(32) time (Trie traversal).
    • Total: O(N log N + Q log Q + (N+Q)*32) ≈ O(N log N + Q log Q)
    • Space: O(N*32) for the Trie.

Summary

The problem asks for the maximum XOR of a given number with an element from an array, subject to a constraint. The naive approach is too slow, so we use a Trie to efficiently find the best candidate for each query. By sorting both nums and queries, and incrementally building the Trie, we ensure that each query only considers eligible numbers. The solution is elegant because it leverages bitwise properties and data structures to reduce time complexity from quadratic to nearly linear.

Code Implementation

class TrieNode:
    def __init__(self):
        self.children = {}
        
class Trie:
    def __init__(self):
        self.root = TrieNode()
        
    def insert(self, num):
        node = self.root
        for i in range(31, -1, -1):
            curr_bit = (num >> i) & 1
            if curr_bit not in node.children:
                node.children[curr_bit] = TrieNode()
            node = node.children[curr_bit]
    
    def getMaxXor(self, num):
        node = self.root
        if not node.children:
            return -1
        max_xor = 0
        for i in range(31, -1, -1):
            curr_bit = (num >> i) & 1
            toggled_bit = 1 - curr_bit
            if toggled_bit in node.children:
                max_xor |= (1 << i)
                node = node.children[toggled_bit]
            else:
                node = node.children.get(curr_bit, node)
        return max_xor

class Solution:
    def maximizeXor(self, nums, queries):
        nums.sort()
        queries = sorted([(mi, xi, idx) for idx, (xi, mi) in enumerate(queries)])
        res = [0] * len(queries)
        trie = Trie()
        idx = 0
        for mi, xi, q_idx in queries:
            while idx < len(nums) and nums[idx] <= mi:
                trie.insert(nums[idx])
                idx += 1
            res[q_idx] = trie.getMaxXor(xi)
        return res
      
struct TrieNode {
    TrieNode* children[2];
    TrieNode() {
        children[0] = children[1] = nullptr;
    }
};

class Trie {
public:
    TrieNode* root;
    Trie() { root = new TrieNode(); }
    
    void insert(int num) {
        TrieNode* node = root;
        for (int i = 31; i >= 0; --i) {
            int bit = (num >> i) & 1;
            if (!node->children[bit])
                node->children[bit] = new TrieNode();
            node = node->children[bit];
        }
    }
    
    int getMaxXor(int num) {
        TrieNode* node = root;
        if (!node->children[0] && !node->children[1]) return -1;
        int maxXor = 0;
        for (int i = 31; i >= 0; --i) {
            int bit = (num >> i) & 1;
            int toggled = 1 - bit;
            if (node->children[toggled]) {
                maxXor |= (1 << i);
                node = node->children[toggled];
            } else if (node->children[bit]) {
                node = node->children[bit];
            }
        }
        return maxXor;
    }
};

class Solution {
public:
    vector<int> maximizeXor(vector<int>& nums, vector<vector<int>>& queries) {
        sort(nums.begin(), nums.end());
        vector<tuple<int, int, int>> q;
        int n = queries.size();
        for (int i = 0; i < n; ++i)
            q.push_back({queries[i][1], queries[i][0], i});
        sort(q.begin(), q.end());
        vector<int> res(n);
        Trie trie;
        int idx = 0;
        for (auto& [mi, xi, qi] : q) {
            while (idx < nums.size() && nums[idx] <= mi) {
                trie.insert(nums[idx]);
                ++idx;
            }
            res[qi] = trie.getMaxXor(xi);
        }
        return res;
    }
};
      
class TrieNode {
    TrieNode[] children = new TrieNode[2];
}

class Trie {
    TrieNode root = new TrieNode();
    
    void insert(int num) {
        TrieNode node = root;
        for (int i = 31; i >= 0; --i) {
            int bit = (num >> i) & 1;
            if (node.children[bit] == null)
                node.children[bit] = new TrieNode();
            node = node.children[bit];
        }
    }
    
    int getMaxXor(int num) {
        TrieNode node = root;
        if (node.children[0] == null && node.children[1] == null) return -1;
        int maxXor = 0;
        for (int i = 31; i >= 0; --i) {
            int bit = (num >> i) & 1;
            int toggled = 1 - bit;
            if (node.children[toggled] != null) {
                maxXor |= (1 << i);
                node = node.children[toggled];
            } else if (node.children[bit] != null) {
                node = node.children[bit];
            }
        }
        return maxXor;
    }
}

class Solution {
    public int[] maximizeXor(int[] nums, int[][] queries) {
        Arrays.sort(nums);
        int n = queries.length;
        int[][] q = new int[n][3];
        for (int i = 0; i < n; ++i) {
            q[i][0] = queries[i][1]; // mi
            q[i][1] = queries[i][0]; // xi
            q[i][2] = i;
        }
        Arrays.sort(q, (a, b) -> Integer.compare(a[0], b[0]));
        int[] res = new int[n];
        Trie trie = new Trie();
        int idx = 0;
        for (int[] query : q) {
            int mi = query[0], xi = query[1], qi = query[2];
            while (idx < nums.length && nums[idx] <= mi) {
                trie.insert(nums[idx]);
                ++idx;
            }
            res[qi] = trie.getMaxXor(xi);
        }
        return res;
    }
}
      
class TrieNode {
    constructor() {
        this.children = {};
    }
}

class Trie {
    constructor() {
        this.root = new TrieNode();
    }
    insert(num) {
        let node = this.root;
        for (let i = 31; i >= 0; --i) {
            let bit = (num >> i) & 1;
            if (!(bit in node.children))
                node.children[bit] = new TrieNode();
            node = node.children[bit];
        }
    }
    getMaxXor(num) {
        let node = this.root;
        if (Object.keys(node.children).length === 0) return -1;
        let maxXor = 0;
        for (let i = 31; i >= 0; --i) {
            let bit = (num >> i) & 1;
            let toggled = 1 - bit;
            if (toggled in node.children) {
                maxXor |= (1 << i);
                node = node.children[toggled];
            } else if (bit in node.children) {
                node = node.children[bit];
            }
        }
        return maxXor;
    }
}

var maximizeXor = function(nums, queries) {
    nums.sort((a, b) => a - b);
    let q = queries.map(([xi, mi], idx) => [mi, xi, idx]);
    q.sort((a, b) => a[0] - b[0]);
    let res = Array(queries.length);
    let trie = new Trie();
    let idx = 0;
    for (let [mi, xi, qi] of q) {
        while (idx < nums.length && nums[idx] <= mi) {
            trie.insert(nums[idx]);
            idx++;
        }
        res[qi] = trie.getMaxXor(xi);
    }
    return res;
};