第一题
给定一个数组,求子数组的最大异或和。
一个数组的异或和为,数组中所有的数异或起来的结果。
简单的前缀树应用
暴力方法:
先计算必须以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 =&#