[comprehensive test question] the difficulty is 4.5/5. Use this question to realize a "countable" Trie

Title Description

This is "1707. The maximum XOR value of the element in the array" on LeetCode. The difficulty is "difficult".

Tag: "persistent dictionary tree", "binary"

Give you an array of nonnegative integers

nums

. There is another query array

queries

, where

queries[i] = [xi, mi]

.

The first

i

The answer to a query is

x_i

And any

nums

No more than

m_i

The maximum value obtained by bitwise XOR (XOR) of the element.

In other words, the answer is max (Num [J] \ {XOR} x_i), where all

j

All meet

nums[j] <= m_i

. If

nums

All elements in are greater than

m_i

, the final answer is

-1

.

Returns an array of integers

answer

As the answer to the query, where

answer.length == queries.length

And

answer[i]

Yes

i

Answer to a query.

Example 1:

Input: nums = [0,1,2,3,4], queries = [[3,1],[1,3],[5,6]]

Output:[3,3,7]

Explanation:
1) 0 And 1 are the only two integers that do not exceed 1. 0 XOR 3 = 3 And 1 XOR 3 = 2 . The greater of the two is 3.
2) 1 XOR 2 = 3.
3) 5 XOR 2 = 7.

Example 2:

Input: nums = [5,2,4,6,6,3], queries = [[12,4],[8,1],[6,3]]

Output:[15,-1,5]

Tips:

  • 1 <= nums.length, queries.length <=
10^5
  • queries[i].length == 2
  • 0 <= nums[j], xi, mi <=
10^9

Basic analysis

Please make sure that you have finished before doing this question 421. Maximum XOR value of two numbers in the array.

Given all the questions in advance, we can use the offline idea (adjust the answer order of the questions) to solve them.

There are two offline ways to solve this problem.

Ordinary Trie

The basic idea of the first method is: do not put all the numbers at one time, but put the numbers that need to participate in the screening every time

Trie

, and then 421. Maximum XOR value of two numbers in the array Similar greedy search logic.

Specifically, we can handle it according to the following logic:

  1. Sort nums from small to large, and sort the second dimension of queries from small to large (save the original subscript mapping relationship before sorting).
  2. Process all queries in sort order [i]:
    1. Before answering each query, store a value less than or equal to queries[i][1]
    Trie

    . Since we have sorted nums in advance, this process only needs to maintain a pointer that moves to the right on nums.

    1. Then, using the greedy idea, query the maximum value that can be found in each query [i] [0], and calculate the XOR and (this process is similar to 421. Maximum XOR value of two numbers in the array Consistent).
    2. Find the subscript of the current query in the original query sequence and save the answer.

code:

class Solution {
    static int N = (int)1e5 * 32;
    static int[][] trie = new int[N][2];
    static int idx = 0;
    public Solution() {
        for (int i = 0; i <= idx; i++) {
            Arrays.fill(trie[i], 0);
        }
        idx = 0;
    }
    void add(int x) {
        int p = 0;
        for (int i = 31; i >= 0; i--) {
            int u = (x >> i) & 1;
            if (trie[p][u] == 0) trie[p][u] = ++idx;
            p = trie[p][u];
        }
    }
    int getVal(int x) {
        int ans = 0;
        int p = 0;
        for (int i = 31; i >= 0; i--) {
            int a = (x >> i) & 1, b = 1 - a;
            if (trie[p][b] != 0) {
                p = trie[p][b];
                ans = ans | (b << i);
            } else {
                p = trie[p][a];
                ans = ans | (a << i);
            } 
        }
        return ans ^ x;
    }
    public int[] maximizeXor(int[] nums, int[][] qs) {
        int m = nums.length, n = qs.length;

        // Use a hash table to save the original order
        Map<int[], Integer> map = new HashMap<>();
        for (int i = 0; i < n; i++) map.put(qs[i], i);

        // Sort nums and queries[x][1] from small to large
        Arrays.sort(nums);
        Arrays.sort(qs, (a, b)->a[1]-b[1]);

        int[] ans = new int[n];
        int loc = 0; // Record which positions in num have been put into Trie
        for (int[] q : qs) {
            int x = q[0], limit = q[1];
            // Store the number less than or equal to limit in Trie
            while (loc < m && nums[loc] <= limit) add(nums[loc++]);
            if (loc == 0) {
                ans[map.get(q)] = -1;    
            } else {
                ans[map.get(q)] = getVal(x);    
            }
        }
        return ans;
    }
}
  • Time complexity: let the length of nums be m and the length of qs be n. The sorting complexity is
O(m\log{m})

and

O(n\log{n})

; Insert all numbers

Trie

And from

Trie

The complexity of finding in is

O(Len)

Len

by

32

. The overall complexity is

O(m\log{m} + n\log{n} + (m + n) * Len)

=

O(m * \max(\log{m}, Len) + n * \max(\log{n}, Len))

.

  • Space complexity:
O(C)

. among

C

Is constant, fixed to

1e5 * 32 * 2

.

Count trie & two points

Another way to "increase the difficulty" is to turn the whole process over: deposit all the information at one time

Trie

And then change the number of no longer participating from

Trie

Remove from. Compared with solution one, this requires us to

Trie

Add a "delete / count" function, and need to implement dichotomy to find the upper bound subscript of the removed element.

Specifically, we can handle it according to the following logic:

  1. Sort nums from large to small, and sort the second dimension of queries from large to small (save the original subscript mapping of the query before sorting).
  2. Process all queries in sort order [i]:
    1. Before answering each query, find the first subscript in nums that satisfies "less than or equal to queries[i][1] through" bisection ", and then change the number before the subscript from
    Trie

    Remove from. Similarly, in this process, we need to use a pointer to record the subscript position of the last deletion to avoid repeated deletion.

    1. Then use the greedy idea to query the maximum value that each query [i] [0] can find. Note that this is to judge whether the current node has been counted. If not, it returns
    -1

    .

    1. Find the subscript of the current query in the original query sequence and save the answer.

code:

class Solution {
    static int N = (int)1e5 * 32;
    static int[][] trie = new int[N][2];
    static int[] cnt = new int[N];
    static int idx = 0;
    public Solution() {
        for (int i = 0; i <= idx; i++) {
            Arrays.fill(trie[i], 0);
            cnt[i] = 0;
        }
        idx = 0;
    }
    // Deposit (v = 1) / delete (v = -1) a number x to Trie
    void add(int x, int v) {
        int p = 0;
        for (int i = 31; i >= 0; i--) {
            int u = (x >> i) & 1;
            if (trie[p][u] == 0) trie[p][u] = ++idx;
            p = trie[p][u];
            cnt[p] += v;
        }
    }
    int getVal(int x) {
        int ans = 0;
        int p = 0;
        for (int i = 31; i >= 0; i--) {
            int a = (x >> i) & 1, b = 1 - a;
            if (cnt[trie[p][b]] != 0) {
                p = trie[p][b];
                ans = ans | (b << i);
            } else if (cnt[trie[p][a]] != 0) {
                p = trie[p][a];
                ans = ans | (a << i);
            } else {
                return -1;
            }
        }
        return ans ^ x;
    }
    public int[] maximizeXor(int[] nums, int[][] qs) {
        int n = qs.length;
        
        // Use a hash table to save the original order
        Map<int[], Integer> map = new HashMap<>();
        for (int i = 0; i < n; i++) map.put(qs[i], i);

        // In descending order
        sort(nums);
        Arrays.sort(qs, (a, b)->b[1]-a[1]);

        // Store all data in Trie
        for (int i : nums) add(i, 1);

        int[] ans = new int[n];
        int left = -1; // In nums, the values subscript "less than or equal to" left have been removed from Trie
        for (int[] q : qs) {
            int x = q[0], limit = q[1];
            // Binary finds the right boundary of the element to be deleted and removes all values before its right boundary from Trie.
            int right = getRight(nums, limit);            
            for (int i = left + 1; i < right; i++) add(nums[i], -1);
            left = right - 1;
            ans[map.get(q)] = getVal(x);
        }
        return ans;
    }
    // Find the right boundary to be deleted
    int getRight(int[] nums, int limit) {
        int l = 0, r = nums.length - 1;
        while (l < r) {
            int mid = l + r >> 1;
            if (nums[mid] <= limit) {
                r = mid;
            } else {
                l = mid + 1;
            }
        }
        return nums[r] <= limit ? r : r + 1;
    }
    // Sort nums in descending order (Java does not have Api to directly support the reverse order of basic type int, which can be ignored in other languages)
    void sort(int[] nums) {
        Arrays.sort(nums);
        int l = 0, r = nums.length - 1;
        while (l < r) {
            int c = nums[r];
            nums[r--] = nums[l];
            nums[l++] = c;
        }
    }
}
  • Time complexity: let the length of nums be m and the length of qs be n, constant
Len = 32

. The sorting complexity is

O(m\log{m})

and

O(n\log{n})

; Insert all numbers

Trie

The complexity of is

O(m * Len)

; Each query needs to go through "dichotomy" to find the boundary, and the complexity is

O(n\log{m})

; In the worst case, all numbers will start from

Trie

Is marked for deletion with complexity of

O(m * Len)

. The overall complexity is

O(m\log{m} + n\log{n} + n\log{m} + mLen)

=

O(m * \max(\log{m}, Len) + n * \max(\log{m}, \log{n}))

.

  • Space complexity:
O(C)

. among

C

Is constant, fixed to

1e5 * 32 * 3

.

explain

Both of these methods adopt "array implementation", and because of the large data range, static is used to optimize the creation of large arrays. The specific "optimization reason" and "class implementation Trie method" can be found in the problem solution 208. Implement Trie (prefix tree) Check. I won't repeat it here.

last

This is No.1707 of our "brush through LeetCode" series. The series began on January 1, 2021. As of the starting date, there are 1916 questions on LeetCode, some of which are locked questions. We will brush all the questions without locks first.

In this series of articles, in addition to explaining the problem-solving ideas, we will also give the most concise code as far as possible. If the general explanation is involved, the corresponding code template will also be.

In order to facilitate students to debug and submit code on the computer, I have established a relevant warehouse: https://github.com/SharingSource/LogicStack-LeetCode .

In the warehouse address, you can see the problem solution link of the series of articles, the corresponding code of the series of articles, the original problem link of LeetCode and other preferred problem solutions.

Posted on Fri, 12 Nov 2021 11:32:10 -0500 by beedie