算法进阶面试题07——求子数组的最大异或和(前缀树)、换钱的方法数(递归改dp最全套路解说)、纸牌博弈、机器人行走问题

这篇博客主要讲解了如何利用前缀树优化求解子数组最大异或和问题,以及通过动态规划解决换钱的方法数问题,并提供了类似题目如纸牌博弈和机器人行走问题的解题思路,旨在帮助提升编程面试中的算法能力。
摘要由CSDN通过智能技术生成

第一题

给定一个数组,求子数组的最大异或和。

一个数组的异或和为,数组中所有的数异或起来的结果。

 

简单的前缀树应用

 

暴力方法:

 

 

先计算必须以i结尾的子数组的异或和,然后再计算机i+1的,以此类推...

 

最暴力的解

复制代码

    public static int getMaxEor1(int[] nums) {
        int maxEor = Integer.MAX_VALUE;
        for (int i = 0; i < nums.length; i++) {
            for (int start = 0; start <= i; start++) {
                int curEor = 0;
                for (int k = start; k <= start; k++) {
                    curEor ^= nums[k];
                }
                Math.max(maxEor, curEor);
            }
        }
        return maxEor;
    }

复制代码

 

怎么优化?

 

异或的运算法则为:0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0(同为0,异为1),这些法则与加法是相同的,只是不带进位,所以异或常被认作不进位加法。

0~i = eor

0~start-1 = eor2

strart~i = eor^eor2

例如

1 0 0 1 1 1 0 1 1 eor

1 0 0 1 1 0 0 0 0 eor2

0 0 0 0 0 1 0 1 1

 

优化方案,准备一个dp辅助数组记录结果。(增加空间)

复制代码

    //记忆化搜索优化(利用之前的计算结果)
    public static int getMaxEor2(int[] nums) {
        int maxEor = Integer.MAX_VALUE;
        int[] dp = new int[nums.length];
        int eor = 0;
        for (int i = 0; i < nums.length; i++) {
            eor ^= nums[i];
            Math.max(maxEor,eor);
            for (int start = 0; start <= i; start++) {
                int curEor = eor ^ dp[start - 1];
                Math.max(maxEor,curEor);
            }
            dp[i] = eor;
        }
        return maxEor;
    }

复制代码

 

结论:

 

 

O(n)的方法

思路:求以i结尾,异或和最大的子数组。0~i、1~i、2~i全部计算出来得到结果,效率很低。

黑盒直到i,里面存了0~0、0~1、0~2...0~i-1的结果,0~i的结果在eor时刻更新的,i希望黑盒可以告诉他,这里面哪个值和eor异或出来最大,那就是答案。

例如eor和0~3异或和是最大的,那么以i结尾的,4~i就是最大的。

 

 

黑盒可以告诉你,0~start ^ eor(0~i) 是最大的,就能得出start^i是最大的。

黑盒用前缀树做,以4位二进制举例,假设0~0、0~1、0~2的值,分别加入到前缀树中

假设求以3结尾情况下,最大异或和,0~3异或结果为0010,我特别希望异或后符号位还是0,后面的尽量1,所以就在前缀树里面寻找适合的路。

 

 

符号位尽量为0,后面的位是0走1,1走0,没得选就将就着走,尽量保持最大化值。

按前缀树的走法,每次选最优,可以找到最大值。

 

符号位为1的情况下,求补码,取反再加一

例如:

1 1 1 1

1 0 0 0 + 1

-1

1 0 1 1

1 1 0 0 + 1

-5

当符号位是1的时候,希望选1的路,让其1^1变成0,除了符号位选择的路有讲究之外,不管正负,接下来的选择是一样的,后面的位尽量都变成1。

所以选择符号位的时候,希望是和符号位的值是一样的,1选1,0选0

 

复制代码

    public static class Node {//前缀树节点
        public Node[] nexts = new Node[2];//只有两个路,0/1
    }

    public static class NumTrie {//前缀树
        public Node head = new Node();

        public void add(int num) {
            Node cur = head;
            //位移,整数是31位
            for (int move = 31; move >= 0; move--) {
                //提取出每个进制里面的数字
                //例如:0101 >> 3 = 0
                //在和1进行与运算
                //0 0 0 0
                //0 0 0 1
                //0 0 0 0 //取出了第一位为0
                int path = ((num >> move) & 1);
                //查看是否有路,没有就新建
                cur.nexts[path] = cur.nexts[path] == null ? new Node() : cur.nexts[path];
                cur = cur.nexts[path];
            }
        }

        //num 0~i eor结果,选出最优再返回
        public int maxXor(int num) {
            Node cur = head;
            int res = 0;
            for (int move = 31; move >= 0; move--) {
                int path = (num >> move) & 1;
                //如果考察符号位希望和path是一样的 1^1=0 0^0=0
                //其他位置,希望是相反的 1^0=1 0^1=1
                int best = move =&#
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值