前缀树及应用两数异或Leetcode之 Maximum XOR of Two Numbers in an Array

题目

  前缀树题目地址:https://leetcode.com/problems/implement-trie-prefix-tree/
  题目地址:https://leetcode.com/problems/maximum-xor-of-two-numbers-in-an-array/

    421. Maximum XOR of Two Numbers in an Array   QuestionEditorial Solution  My Submissions
Total Accepted: 854
Total Submissions: 3457
Difficulty: Medium
Contributors: shen5630
Given a non-empty array of numbers, a0, a1, a2, … , an-1, where 0 ≤ ai < 231.

Find the maximum result of ai XOR aj, where 0 ≤ i, j < n.

Could you do this in O(n) runtime?

Example:

Input: [3, 10, 5, 25, 2, 8]

Output: 28

Explanation: The maximum result is 5 ^ 25 = 28.

前缀树

前缀树的实现

  普通的前缀树的实现,插入的一个字符串,如同百度搜索的那种感觉,体会一下。所以需要用一个Map来实现Prefix继续向下,当然也可以用26个字母的数组。其实也挺好的,只是比较占用内存。代码如下所示:

import java.util.HashMap;

class TrieNode {
    boolean isWord;
    HashMap<Character, TrieNode> children;

    // Initialize your data structure here.
    public TrieNode() {
        children = new HashMap<>();
    }
}

public class Trie {
    private TrieNode root;

    public Trie() {
        root = new TrieNode();
    }

    // Inserts a word into the trie.
    public void insert(String word) {
        char[] arrays = word.toCharArray();
        TrieNode head = root;
        for (int i = 0; i < arrays.length; i++) {
            //没有则添加
            if (!head.children.containsKey(arrays[i])) {
                head.children.put(arrays[i], new TrieNode());
            }
            head = head.children.get(arrays[i]);//转到子节点
            if (i == arrays.length - 1) {
                head.isWord = true;
            }
        }
    }

    // Returns if the word is in the trie.
    public boolean search(String word) {
//        char[] arrays = word.toCharArray();
        TrieNode head = root;
        for (int i = 0; i < word.length(); i++) {
            head = head.children.get(word.charAt(i));
            if (head == null) return false;
        }
        return head.isWord;
    }

    // Returns if there is any word in the trie
    // that starts with the given prefix.
    public boolean startsWith(String prefix) {
//        char[] arrays = prefix.toCharArray();
        TrieNode head = root;
        for (int i = 0; i < prefix.length(); i++) {
            head = head.children.get(prefix.charAt(i));
            if (head == null) return false;
        }
        return true;
    }
}

// Your Trie object will be instantiated and called as such:
// Trie trie = new Trie();
// trie.insert("somestring");
// trie.search("key");

本题题解

  • 假设有这样一个数组[5 1 4 3 0 2] 。那么它们的二进制表示为
101  001  100   011   000   010
  • 要找到最大异或和,则尽量使两个数在同一bit位上差异巨大,所以用到分组的想法:最开始位为1的分为一组,为0的分为一组。这也是前缀树的思想。分组后如下所示
101  100
001  011  000  010
  • 看起来还不错,但是依然无法看出来哪两个数异或的值是最大的,为此继续向下分组。这次按照 11,10,01,00分组:
11:
10:  101  100
01:  011  010
00:  000  001
101: 101
100: 100
011: 011
010: 010
101 ^ 010 =7

这种思想,加上前缀树,思考出这样的一种方式去求解问题:

  • 将每个数加入前缀树中,每个节点只有0和1,两个方向。


  • 对于某一个数value,找与其同等bit位的另一个分支,如果这样的分支存在,则将这个分支值加入到要求得异或数中,如果这样的分支不存在,将bit位上的值加入到异或数中。
    例如 : value 的二进制表示为 :
    0000 0000 1000 0010 0000 0110 0000 0101
    在前缀树中能找到的与value异或最大的数xorValue可能是 :
    0111 1111 0111 1101 1111 1001 1111 1010;


-由上一点得到的异或数为与value相异或的最大数xorValue了。然后

 res = Math.max(res, xorValue ^ value);

Code

public class Solution {
   /**
     * 计算数组间两数异或的最大值
     *
     * @param nums
     * @return
     */
    public int findMaximumXOR(int[] nums) {
        /*前缀树*/
        int res = 0;
        Node root = new Node();
        for (int num : nums) {
            buildTrie(root, num);
        }
        for (int value : nums) {
            int xorValue = findMatchXor(root, value);
            res = Math.max(res, xorValue ^ value);
        }
        return res;
    }

    /**
     * 找到与num匹配的在前缀树中最大的数
     *
     * @param root
     * @param num
     * @return
     */
    private int findMatchXor(Node root, int num) {
        int res = 0;
        Node head = root;
        for (int i = 30; i >= 0; i--) {
            int bit = num & (1 << i);
            int flag = bit == 0 ? 0 : 1;
            /*异或的最大值就是找到与num同等位相反的,这样才是最大值*/
            /*如果不存在这样分支,则找与num同等位相同的*/
            if (head.next[1 - flag] == null) {
                head = head.next[flag];
                /*加*/
                res += flag << i;
            } else {
                head = head.next[1 - flag];
                /*加*/
                res += (1 - flag) << i;
            }
        }
        return res;
    }

    /**
     * 构建前缀树
     *
     * @param root
     * @param x
     */
    private void buildTrie(Node root, int x) {
        /*建前缀树的过程,需要31位 不包括符号位*/
        Node head = root;
        for (int i = 30; i >= 0; i--) {
            int bit = x & (1 << i);/*判断第 i bit位*/
            int flag = bit == 0 ? 0 : 1;
            /*前缀树的基本构建过程,如果没有则添加,有的继续向下*/
            if (head.next[flag] == null) {
                head.next[flag] = new Node();
            }
            head = head.next[flag];
        }
    }

    private static class Node {
        Node[] next;

        public Node() {
            /**0 和 1 */
            next = new Node[2];
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值