前缀树

Posted by 令德湖周杰伦 on 03-15,2024

1. 结构

2. 用途

3. 应用

3.1 子数组的最大异或和

题目:
数组异或和的定义:把数组中所有的值异或起来的到值
给定一个int数组,其中有正有负,有0,求其中子数组的最大异或和
eg:
{3}, 最大异或和就是3
{3,-28,-29,2}, 子数组为{-28,-29},最大异或和为7

3.1.1 暴力解

    private int process(int[] nums){
        if(nums.length == 1){
            return nums[0];
        }
        //子数组问题,遍历以i结尾的所有子数组,比大小,arr[...i]
        int ans=0;
        for (int i = 0; i < nums.length; i++) {
            for (int start = 0; start <= i; start++) {
                int sum=0;
                //对arr[start...i]的数子进行累加,比大小
                for (int j = start; j <= i; j++) {
                    sum ^= nums[j];
                }
                ans = Math.max(ans, sum);
            }
        }
        return ans;
    }

3.1.2 根据异或和的性质进行优化

思路:
xorSum[0...s] ^ xorSum[s+1 .... i] = xorSum[0...i]
=>
xorSum[s .... i] = xorSum[0...s-1] ^ xorSum[0...i]

    private int processUpgrade(int[] nums){
        if(nums.length == 1){
            return nums[0];
        }
        //记录xorSum[0...i]的异或和
        int[] preSum = new int[nums.length];
        preSum[0] = nums[0];
        for (int i = 1; i < nums.length; i++) {
            preSum[i] =  preSum[i-1] ^ nums[i];
        }

        //子数组问题,遍历以i结尾的所有子数组,比大小,arr[...i]
        int ans=0;
        for (int i = 0; i < nums.length; i++) {
            for (int start = 0; start <= i; start++) {
                //对arr[start...i]的数子进行累加,比大小
                int sum= (start -1 == -1) ? (0 ^ preSum[i]) : (preSum[start-1] ^ preSum[i]);
                ans = Math.max(ans, sum);
            }
        }
        return ans;
    }

3.1.3 利于前缀树的路径进行贪心优化

    private int processTrie(int[] nums){
        if(nums.length == 1){
            return nums[0];
        }

        //记录xorSum[0...i]的异或和
        int[] preSum = new int[nums.length];
        preSum[0] = nums[0];
        for (int i = 1; i < nums.length; i++) {
            preSum[i] =  preSum[i-1] ^ nums[i];
        }

        NumTrie numTrie = new NumTrie();
        numTrie.add(0);

        //子数组问题,遍历以i结尾的所有子数组,比大小,arr[...i]
        int ans=0;
        for (int i = 0; i < nums.length; i++) {
            //通过前缀数,中记录了0,0-1,0-2,...0-i的前缀合路径,返回这些路径中和(当前前缀和)结合最大的值
            int preMax = numTrie.maxXor(preSum[i]);
            ans = Math.max(ans, preMax);
            //将当前前缀和,加入到前缀树路径
            numTrie.add(preSum[i]);
        }
        return ans;
    }

结构和方法:

    public static class Node{
        public Node[] nexts = new Node[2];
    }

    public static class  NumTrie{

        public Node head = new Node();

        public void add(int num){
            Node cur = head;
            //从高位开始取,构建path,需要右移
            for (int i = 31; i >0; i--) {
               int path = (num >> i) & 1;
                //没有路径创建,又的话就继续遍历添加
                cur.nexts[path] =  head.nexts[path] == null ? new Node() : head.nexts[path];
                cur = cur.nexts[path];
            }

        }

        /**
         * 将sum走最优路径后,返回最大异或和
         * @param sum
         * @return
         */
        public int maxXor(int sum){
            Node cur = head;
            int ans = 0;
            //从高位开始取,构建path,需要右移
            for (int i = 31; i >0; i--) {
                int path = (sum >> i) & 1;
                //贪心:找最佳路径,如果是符号位就是一样的,否则取异或的
                int best = i==32 ? path : path^1;
                //实际能走的路径
                best = cur.nexts[path]!=null ? best : best^1;
                //best ^ path 为计算出来的最大位值,还需要移动回去
                ans = ans | (best ^ path) << i;
            }
            return ans;
        }

    }