Algorithm 查找给定范围内所有数字的异或
您会得到一个大范围[a,b],其中“a”和“b”通常介于1和4000000000之间(包括1和4000000000)。你必须找出给定范围内所有数字的异或 这个问题被用于TopCoder SRM。我在比赛中看到了一个提交的解决方案,我不知道它是如何工作的 有人能解释一下获胜的解决方案吗:Algorithm 查找给定范围内所有数字的异或,algorithm,Algorithm,您会得到一个大范围[a,b],其中“a”和“b”通常介于1和4000000000之间(包括1和4000000000)。你必须找出给定范围内所有数字的异或 这个问题被用于TopCoder SRM。我在比赛中看到了一个提交的解决方案,我不知道它是如何工作的 有人能解释一下获胜的解决方案吗: 长f(长a){ long-long-res[]={a,1,a+1,0}; 返回res[a%4]; } long-long getXor(long-long a,long-long b){ 申报表f(b)^f(a-
长f(长a){
long-long-res[]={a,1,a+1,0};
返回res[a%4];
}
long-long getXor(long-long a,long-long b){
申报表f(b)^f(a-1);
}
这里,
getXor()
是计算传递范围[a,b]中所有数字的xor的实际函数,“f()”是一个辅助函数。这是一个非常聪明的解决方案——它利用了在运行xor时存在结果模式这一事实。f()
函数从[0,a]计算XOR总运行。请查看此表中的4位数字:
0000 <- 0 [a]
0001 <- 1 [1]
0010 <- 3 [a+1]
0011 <- 0 [0]
0100 <- 4 [a]
0101 <- 1 [1]
0110 <- 7 [a+1]
0111 <- 0 [0]
1000 <- 8 [a]
1001 <- 1 [1]
1010 <- 11 [a+1]
1011 <- 0 [0]
1100 <- 12 [a]
1101 <- 1 [1]
1110 <- 15 [a+1]
1111 <- 0 [0]
0000在法塔莱罗的伟大答案的基础上增加了一行返回f(b)^f(a-1)代码>可以更好地解释。简而言之,这是因为XOR具有以下奇妙的特性:
- 它是关联性的-将括号放在任何需要的地方
- 它是可交换的,这意味着你可以移动操作员(他们可以“通勤”)
这两个方面都在发挥作用:
(a ^ b ^ c) ^ (d ^ e ^ f) = (f ^ e) ^ (d ^ a ^ b) ^ c
- 它将自身反转
像这样:
a ^ b = c
c ^ a = b
加法和乘法是其他结合/交换运算符的两个示例,但它们本身并不反转。那么,为什么这些属性很重要呢?好的,一个简单的方法是把它扩展到它真正的样子,然后你可以看到这些属性在起作用
首先,让我们定义我们想要的,并称之为n:
n = (a ^ a+1 ^ a+2 .. ^ b)
如果有帮助,可以将XOR(^)看作是一个add
我们还要定义函数:
f(b) = 0 ^ 1 ^ 2 ^ 3 ^ 4 .. ^ b
b
大于a
,因此只需安全地放入几个额外的括号(我们可以这样做,因为它是关联的),我们也可以这样说:
f(b) = ( 0 ^ 1 ^ 2 ^ 3 ^ 4 .. ^ (a-1) ) ^ (a ^ a+1 ^ a+2 .. ^ b)
这简化为:
f(b) = f(a-1) ^ (a ^ a+1 ^ a+2 .. ^ b)
f(b) = f(a-1) ^ n
接下来,我们利用反转性质和可换性给出了幻线:
n = f(b) ^ f(a-1)
如果你一直把异或看作是加法,那么你就会在那里加入减法。XOR之于XOR,就像加法之于减法一样
我自己是怎么想到这个的?
记住逻辑运算符的属性。如果有帮助的话,可以像加法或乘法一样处理它们。and(&)、xor(^)和or(|)是关联的,这感觉很不寻常,但它们是关联的
首先运行naive实现,在输出中查找模式,然后开始查找确认模式为真的规则。进一步简化您的实现并重复。这可能是原始创建者采取的路线,突出的是它不是完全最优的(即使用switch语句而不是数组)。我发现下面的代码也与问题中给出的解决方案一样工作
也许这有点优化,但这正是我从观察重复中得到的,就像在接受的答案中给出的一样
我想知道/理解给定代码背后的数学证明,就像@Luke Briggs在回答中解释的那样
这是JAVA代码
public int findXORofRange(int m, int n) {
int[] patternTracker;
if(m % 2 == 0)
patternTracker = new int[] {n, 1, n^1, 0};
else
patternTracker = new int[] {m, m^n, m-1, (m-1)^n};
return patternTracker[(n-m) % 4];
}
我已经用递归解决了这个问题。对于每次迭代,我只是将数据集分成几乎相等的部分
public int recursion(int M, int N) {
if (N - M == 1) {
return M ^ N;
} else {
int pivot = this.calculatePivot(M, N);
if (pivot + 1 == N) {
return this.recursion(M, pivot) ^ N;
} else {
return this.recursion(M, pivot) ^ this.recursion(pivot + 1, N);
}
}
}
public int calculatePivot(int M, int N) {
return (M + N) / 2;
}
让我知道你对解决方案的想法。很高兴收到改进反馈。建议的解决方案以0(logn)复杂度计算XOR
感谢您支持从0到N的异或。给定的代码需要修改如下:
int f(int a) {
int []res = {a, 1, a+1, 0};
return res[a % 4];
}
int getXor(int a, int b) {
return f(b) ^ f(a);
}
最小范围阈值为1,而不是10@PenchoIlchev它是否包含0是一种有争议的问题--(n^0)==n@rajneesh2k10好的,在4次运行中(从4的倍数开始),除了最低的位之外,所有的位都是相同的,所以它们在相互抵消或具有其原始值之间交替。的确,最低位周期每2次,但0^1==1(即,它们不会取消)。最低的两个之所以特别是因为(00^01^10^11)=00。换句话说,每循环4个值,就返回到0,因此可以取消所有这些循环,这就是为什么%4是重要的。@Pandreia
有2个,而不是0。该列是正在运行的xor,1 xor 2是3,因此该行中的当前值在我看来是正确的。我稍微编辑了一下你的问题。我们不介意解释一些代码的原因,但是我们不需要一个新的列表来解决这个问题。交给TopCoder吧。@Kev没问题!我写这篇文章是因为有些人喜欢用自己的方式,而不是解释已经写好的东西。任何新的想法都不是浪费……;)这让我想起了我去年在大学上的离散数学课程。有趣的日子。读了这篇文章后,我立刻想到了这一点。这篇文章的计算复杂度与普通的m^(m+1)^…^(n-1)^n计算。这是0(n)。