这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战
题目
关键的提示
1 <= maximumBit <= 20 0 <= nums[i] < 2^maximumBit^ nums 中的数字已经按 升序 排好序。
前置知识
-
异或(XOR)的含义:
- A XOR B在编程中一般以 A^B来表示
- 含义是:A和B转化为二进制时,两位相同为0,不同为1,返回各位的结果。
-
取反(or)的含义:
- orB 一般表示为 ~B。
- 含义是:将B中二进制的每一位改为不同的数。(0改为1,1改为0)
分析
既然A^B含义是两位不同为1相同为0,那么:
- 所求的 :nums[0] XOR nums[1] XOR ... XOR nums[nums.length-1] XOR k 中的k
只要尽可能地将nums[0] XOR nums[1] XOR ... XOR nums[nums.length-1] 中的二进制结果按位取反即可,如果超出了题目指定的2^maximumBit的大小,那么我们覆盖可以覆盖的就可以了。
-
同时,注意到数组会依次删除最后一个,那么其实倒数第n次的结果就包含前n个数,倒序放入数组。
-
按位取反的运算符是~,同时注意到nums[i]<2^maximumBit^,那么直接取反,并截去前面大于maximumBit位的数就可以了。
-
而截去前n位,一个简单的做法就是左移32-n位再右移32-n位。
编码
分析完毕,依葫芦画瓢,首先是取数的:
public static int getBinaryReverse(int resource,int bit){
int tar = ~resource;
//左移再右移,把多出来的位删掉
tar <<= 32-bit;
tar>>>= 32-bit;
return tar;
}
随后是上层的:
public static int[] getMaximumXor(int[] nums, int maximumBit) {
int n = nums.length;
int[] res = new int[n];
int xor = 0;
for (int i = n-1; i >= 0; i--) {
xor ^= nums[n-i-1];
res[i] = getBinaryReverse(xor,maximumBit);
}
return res;
}
这里依次反着来,这样子每次XOR都只需要加上一个数。
这里就是:
- O(n)的时间复杂度(数组每个元素都只访问了1次),
- O(n)的空间复杂度(毕竟我们新建了一个数组)
当然这个空间复杂度我们可以用原来的数组来做(原地转换)利用到上面只访问一次的特性,填完之后把数组翻过来,毕竟题目没说不能改:
public static int[] getMaximumXor(int[] nums, int maximumBit) {
int xor = 0;
for (int i = 0; i < nums.length; i++) {
xor ^= nums[i];
nums[i] = getBinaryReverse(xor,maximumBit);
}
reverseArr(nums);
return nums;
}
public static void reverseArr(int[] nums){
int left = 0 ,right = nums.length-1;
while(left<=right){
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
left++;
right--;
}
}
使用额外数组的结果:
执行用时:3 ms, 在所有 Java 提交中击败了54.74%的用户
内存消耗:53.4 MB, 在所有 Java 提交中击败了95.79%的用户
使用原地数组的结果:
执行用时:5 ms, 在所有 Java 提交中击败了30.53%的用户
内存消耗:50.7 MB, 在所有 Java 提交中击败了96.84%的用户
进一步分析
leetCode上更好的解法,实际上是对于上面位运算的一个改进:
public static int[] getMaximumXor(int[] nums, int maximumBit) {
int n = nums.length;
int cur = 0;
int[] ans = new int[n];
int mask = (1 << maximumBit) - 1;
for(int i = 0; i < n; i++) {
cur = cur ^ nums[i];
ans[n - i - 1] = cur ^ mask;
}
return ans;
}
思考一下,其实没有必要取反再左右移动,直接上掩码即可。
- 因为这里掩码必然每一位都会是1,那么这里异或一次就可以得到结果。