1486. 数组异或操作
思路:
就我的小脑袋瓜,除了for循环遍历,还能想到什么高级的数学方法呀?
我下意识想到用异或运算的性质,但是没想到用的哪个。看了题解确信了数学方法我做不出来 。
代码:
class Solution {
public:
int xorOperation(int n, int start) {
int res=0;
for(int i=0;i<n;++i){
res ^= start+2*i;
}
return res;
}
};
看这题解看不懂,睡一觉,迷迷糊糊想明白了,就突然清醒爬起来记录了。
首先我们考虑把start+2 * i中的2提取出来,那么就是 start / 2 + i => s+i,那么start丢失了原来的最后一位。
当我们进行异或 原式子(start+2 * 0) ^ (start+2 * 1).... 右移1位后(s+0) ^ (s+1)... * 2,即使我们乘以2 ,但其实这样的变换是不相等的,因为我们将start右移一位后,在左移回来我们是丢失了最后一位的数的,左移回来已经全为0.所以,我们还要加上原式进行异或后的最后一位,就是start+2 * i 各项异或后的最后一位。
那么问题来了,如何求最后一位呢?这里就需要找规律了。当start为偶数时,2 ^ 4 ^ 6...,不论有偶数个还是奇数个相异或,我们其实可以得到一个结论,最后一位是0。当start为奇数是,此时如果n为奇数,及 某个奇数+2 * i 有奇数项个向异或,那么我们得到结论,在最后一位为1,举个栗子 1 ^ 3 ^ 5,其二进制最后一位为1. 同理当n为偶数时,异或结果二进制最后一位为0,因为奇数个为1,那么再来一个奇数自然相异或就为0了(一个奇数的二进制最后一位一定为1)。
OK,我们得到规律后,如何转化为逻辑表达式呢? n&start,如果两个都为奇数,那么必然最后一位为1,否则任何一个不为奇数最后一位都为0,最后在和1相与,就能提取最后一位了。那么 lastbit=n&start&1.
接下来还是一个规律,可以简化不用一个个异或。并未后面最重要的算法做准备。
我们把0-9的二进制数列出来,然后在列出0-0,0-1,0-2...0-9的异或值,0-9的异或值是什么意思?就是0 ^ 1 ^ 2 ^ 3 ^ 4... ^ 9得到的结果。
n 二进制 异或值(0-n)
0 0000 0000
1 0001 0001
2 0010 0011
3 0011 0000
4 0100 0100
5 0101 0001
6 0110 0111
7 0111 0000
8 1000 1000
9 1001 0001
通过对比我们可以发现 n % 4 == 0的异或值都为n即其本身,如4,8;n % 4 == 1的数异或值都为1,如1,9;n % 4 == 2的异或值都为n+1,如2,6.最后n % 4 == 3的异或值都为0.
上述的结论会服务我们最后的算法。
根据异或的性质,我们有3 ^ 4 ^ 5 ^ 6 ^ 7 ^ 8 ^ 9=(1 ^ 2) ^ (1 ^ 2 ^ 3 ^ 4 ^ 5 ^ 6 ^ 7 ^ 8 ^ 9).因为自己和自己异或就为0了嘛,0和任意数异或都为任意数本身,所以就把1^2这个重复的给弄为0了,还不影响后面的3-9的异或。
好的,那我们的求解的 s+i 异或与此相同,(s+0) ^ (S+1)... ^ (s+n-1) = calxor(s-1) ^ calxor(s+n-1).
有朋友要问了:哎,为什么这里是s+n-1呢?答:因为题目说n的下标从0开始,故从0开始数到n-1才是n。
calxor就是我们上面统计出来的n%4的规律,对比我们举得例子,calxor(s-1)=(1 ^ 2),同样calxor(s+n-1)=(1 ^ 2.. ^ 9)。
那么我们就能得到答案了,我们把n个数的异或转换为了两个数的异或,O(n)的时间复杂度降到了O(1)
那么我们算除了s+i各项异或的结果后,再把lastbit加上即可。
代码:
class Solution {
public:
int calxor(int n){
switch(n%4){
case 0: return n;
case 1: return 1;
case 2: return n+1;
}
return 0; //n%4==3的情况
}
int xorOperation(int n, int start) {
int lasebit = n&start&1; //求解最后一个比特
int s=start>>1; //转化为s+i
int res = calxor(s-1)^calxor(s+n-1); //就是我们说的那个算法
return (res<<1)+lasebit;
}
};