算法(6)-数组异或操作
1. 题目
1486. 数组异或操作
难度简单83
给你两个整数,
n
和start
。数组
nums
定义为:nums[i] = start + 2*i
(下标从 0 开始)且n == nums.length
。请返回
nums
中所有元素按位异或(XOR)后得到的结果。示例 1:
输入:n = 5, start = 0 输出:8 解释:数组 nums 为 [0, 2, 4, 6, 8],其中 (0 ^ 2 ^ 4 ^ 6 ^ 8) = 8 。 "^" 为按位异或 XOR 运算符。
示例 2:
输入:n = 4, start = 3 输出:8 解释:数组 nums 为 [3, 5, 7, 9],其中 (3 ^ 5 ^ 7 ^ 9) = 8.
示例 3:
输入:n = 1, start = 7 输出:7
示例 4:
输入:n = 10, start = 5 输出:2
提示:
1 <= n <= 1000
0 <= start <= 1000
n == nums.length
2. 题解
2.1 方法一:模拟
思路
按照题意模拟即可:
代码
class Solution {
public int xorOperation(int n, int start) {
int ans = 0;
for (int i = 0; i < n; ++i) {
ans ^= (start + i * 2);
}
return ans;
}
}
复杂度分析
- 时间复杂度:O(n)。这里用一重循环对 n 个数字进行异或。
- 空间复杂度:O(1)。这里只是用了常量级别的辅助空间。
2.2 方法二:数学
记 ⊕ 为异或运算,异或运算满足以下性质:
- x⊕x=0;
- x⊕y=y⊕x(交换律);
- (x⊕y)⊕z=x⊕(y⊕z)(结合律);
- x⊕y⊕y=x(自反性);
- ∀i∈Z,有 4i⊕(4i+1)⊕(4i+2)⊕(4i+3)=0。
在本题中,我们需要计算 start⊕(start+2i)⊕(start+4i)⊕⋯⊕(start+2(n−1))。
观察公式可以知道,这些数的奇偶性质相同,因此它们的二进制表示中的最低位或者均为 1,或者均为 0。于是我们可以把参与运算的数的二进制位的最低位提取出来单独处理。当且仅当 start 为奇数,且 n 也为奇数时,结果的二进制位的最低位才为 1。
此时我们可以将公式转化为 (s⊕(s+1)⊕(s+2)⊕⋯⊕(s+n−1))×2+e,其中 s=start/2,e 表示运算结果的最低位。即我们单独处理最低位,而舍去最低位后的数列恰成为一串连续的整数。
这样我们可以描述一个函数 sumXor(x),表示0⊕1⊕2⊕⋯⊕x。利用异或运算的性质 5,我们可以将计算该函数的复杂度降低到 O(1),因为以 4i 为开头的连续四个整数异或的结果为 0,所以 sumXor(x) 可以被表示为:
$$ \text{sumXor}(x)= \begin{cases} x,& x=4k,k\in Z\\ (x-1) \oplus x,& x=4k+1,k\in Z\\ (x-2) \oplus (x-1) \oplus x,& x=4k+2,k\in Z\\ (x-3) \oplus (x-2) \oplus (x-1) \oplus x,& x=4k+3,k\in Z\\ \end{cases} $$
我们可以进一步化简该式:
$$ \text{sumXor}(x)= \begin{cases} x,& x=4k,k\in Z\\ 1,& x=4k+1,k\in Z\\ x+1,& x=4k+2,k\in Z\\ 0,& x=4k+3,k\in Z\\ \end{cases} $$
这样最后的结果即可表示为:
$$ (\text{sumXor}(s-1) \oplus \text{sumXor}(s+n-1))\times 2 + e) $$
代码
class Solution {
public int xorOperation(int n, int start) {
int s = start >> 1;
// int mod = start % 2;
// int e = (n & 1) == 1 ? mod : 0;
int e = n & start & 1;
int ret = sumXor(s - 1) ^ sumXor(s + n - 1);
return ret << 1 | e;
}
public int sumXor(int x) {
if (x % 4 == 0) {
return x;
}
if (x % 4 == 1) {
return 1;
}
if (x % 4 == 2) {
return x + 1;
}
return 0;
}
}
复杂度分析
- 时间复杂度:O(1)。我们只需要常数的时间计算出结果。
- 空间复杂度:O(1)。我们只需要常数的空间保存若干变量。
3. 思考
首先,下面的计算公式中前两项,运用了前缀和的思路,即
$$ \begin{cases} \text{sumXor}(s-1)\quad\quad\ = 0 \oplus 1 \oplus 2 \oplus ··· \oplus {(s-2)} \oplus {(s-1)}\\ \text{sumXor}(s+n-1)) = 0 \oplus 1 \oplus 2 \oplus ··· \oplus {(s-2)} \oplus {(s-1)} \oplus {(s)} \oplus {(s+1)} \oplus ···\oplus {(s+n-2)}\oplus {(s+n-1)} \end{cases} $$
故,按照性质1,这两项进行异或时,相当于做前缀和的减法,即:
$$ \text{sumXor}(s-1) \oplus \text{sumXor}(s+n-1) =[0 \oplus 1 \oplus ··· \oplus {(s-1)}] \oplus [0 \oplus 1 \oplus ··· \oplus {(s-1)} \oplus {s} \oplus ···\oplus {(s+n-2)}\oplus {(s+n-1)}] \\ \quad\quad \quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad =[0 \oplus 0 \oplus 1 \oplus 1 \oplus ··· \oplus {(s-1)} \oplus {(s-1)}] \oplus {s} \oplus ···\oplus {(s+n-2)}\oplus {(s+n-1)} \\ \quad\quad \quad\quad \quad\quad \quad\quad \quad\quad \quad\quad \quad\quad = {s} \oplus {(s+1)} \oplus···\oplus {(s+n-2)}\oplus {(s+n-1)} $$
对于 e 计算,则是出于以下原因点:
对于二进制数,因为每次都是给 start 加上 2 的倍数,所以,最后得到的数组中所有数字二进制的最后一位一直是不变的,即 start 如果是偶数,则数组中所有数都是偶数,最后一位全为0,如果是奇数,则数组中所有数都是奇数,最后一位全为1。
因此可知,只有 start为奇数,并且n为奇数时,e的最后一位才能为1,其他情况全为0。
又因为我们只需要最后一位,所以还要按位与上1,即:n & start & 1
综合
思考1
和思考2
,我们可以把题目做以推广,即:给你三个整数,
n
、start
和k
。数组
nums
定义为:nums[i] = start + k*i
(i从0开始,k=2^m(m>=1))且n == nums.length
。请返回
nums
中所有元素按位异或(XOR)后得到的结果。题目的解法和上面的一样,结果 result 可以表示为:
$$ result =\text{sumXor}(s-1) \oplus \text{sumXor}(s+n-1))\times k + e \\ 其中 s=\lfloor \frac{\textit{start}}{k} \rfloor,e=(start \% k) \oplus (start \% k) \oplus ···\oplus(start \% k)\oplus(start \% k) $$
而上面的e等于n个start对k的余数异或,可参照
思考2
和性质1
, 得到int mod = start % k; int e = (n & 2 == 1) ? mod : 0;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。