异或运算简介
符号
异或运算即exclusive OR,通常写作XOR,数学符号⊕,程序符号^。
运算
相同为0(1^1=0, 0^0=0
),不同为1(1^0=1, 0^1=1
)
性质
① 自反性:a ^ b ^ b = a
② 无序性:a ^ b ^ c ^ d = b ^ d ^ a ^ c
③ 可移项性:a ^ b = c 可移项为 a = b ^ c,移项时无需改变符号
④ 位独立性:每一位的异或互相独立,比如 1010 ^ 1110 = 0100
题目1:只出现一次的数字
题目描述
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
输入样例: [4,1,2,1,2]
输出样例:4
异或运算"自反性+无序性"的经典例题。
class Solution {
public int singleNumber(int[] nums) {
int res = 0;
for(int n : nums) {
res ^= n;
}
return res;
}
}
题目2:子数组的异或和
题目描述
给定一个非空整数数组,请你求出其指定子数组的所有元素的异或之和。给出的子数组有可能有多个,请你分别求出。
输入样例:arr = [1,3,4,8], queries = [[0,1],[1,2],[0,3],[3,3]]
输出样例:[8,0,4,4]
使用"异或前缀和"(preXorSum)———异或运算也是可以使用前缀和的!!!
甚至更加优美,因为异或移项时无需改变符号,前缀和还要借助减号。
class Solution {
public int[] xorQueries(int[] arr, int[][] queries) {
int len = arr.length;
// 构造异或前缀和数组
int[] preXorSum = new int[len + 1];
for (int i = 0; i < len; i++) {
preXorSum[i + 1] = preXorSum[i] ^ arr[i];
}
// 使用异或前缀和数组
int[] res = new int[queries.length];
for (int i = 0; i < queries.length; i++) {
res[i] = preXorSum[queries[i][1] + 1] ^ preXorSum[queries[i][0]];
}
return res;
}
}
题目3:解码异或加密数组I
题目描述
给定一个非空整数数组,它经过加密后(encoded[i] = arr[i] XOR arr[i + 1] )转化为一个长度为n-1的加密数组encoded。比如arr = [1,0,2,1] 经加密后得到 encoded = [1,2,3]。现在,已知加密数组encoded和原数组的第一个元素first,请你解密出完整原数组。
输入样例:encoded = [6,2,7,3], first = 4
输出样例:[4,2,0,7,4]
利用可移项性("a^b=c --> a=c^b"),直接一个"连锁反应"求出原数组。
class Solution {
public int[] decode(int[] encoded, int first) {
int len = encoded.length;
int[] res = new int[len + 1];
res[0] = first;
for (int i = 0; i < len; i++) {
res[i + 1] = encoded[i] ^ res[i];
}
return res;
}
}
题目4:解码异或加密数组II
题目描述
给定一个非空整数数组,它经过加密后(encoded[i] = arr[i] XOR arr[i + 1] )转化为一个长度为n-1的加密数组encoded。比如arr = [1,0,2,1] 经加密后得到 encoded = [1,2,3]。现在,你只知道原数组长度n为奇数,且原数组是前 n 个正整数的排列,请你解密出完整原数组。
输入样例:encoded = [6,5,4,6]
输出样例:[2,4,1,5,3]
我们依旧想利用上面的"连锁反应",但是我们不知道原数组的第一个元素,怎么办?巧妙的把它求出来呗。
因为原数组的长度为奇数,我们假设为5(原数组是1、2、3、4、5的组合),则加密数组长度为4
看下面的式子:
encode[0] = code[0] ^ code[1]
encode[1] = code[1] ^ code[2]
encode[2] = code[2] ^ code[3]
encode[3] = code[3] ^ code[4]
取奇数行的式子,并异或:
encode[1] ^ encode[3] = code[1] ^ code[2] ^ code[3] ^ code[4]
encodeTotal = encode[1] ^ encode[3]
再已知:
code[0] ^ code[1] ^ code[2] ^ code[3] ^ code[4] = 1 ^ 2 ^ 3 ^ 4 ^ 5 = codeTotal
两式联立很容易得到:
code[0] = codeTotal ^ encodeTotal
之后使用第一题的连锁反应即可。
class Solution {
public int[] decode(int[] encoded) {
// 没写注释,代码其实是对上面的思路的模拟
int len = encoded.length + 1;
int[] code = new int[len];
int codeTotal = 0;
int encodeTotal = 0;
for (int i = 0; i < len; i++) {
codeTotal ^= (i + 1);
}
for (int i = 1; i < len - 1; i += 2) {
encodeTotal ^= encoded[i];
}
code[0] = encodeTotal ^ codeTotal;
for (int i = 1; i < len; i++) {
code[i] = code[i - 1] ^ encoded[i - 1];
}
return code;
}
}
题目5:数组中两数最大的异或值
题目描述
给定一个非空整数数组,返回 a ^ b 的最大值,其中a和b都是数组中的元素。
并且,0 <= nums[i] <= 2的31次方 - 1。
输入样例:nums = [3,10,5,25,2,8]
输出样例:28
这里用到了上面未曾用过的一个异或性质———"位独立性"。
数组中的所有数字最大31位,我们就把所有数字都看成31位的二进制数;
在异或运算时 ,各个独立进行运算,因此我们用这些二进制数构造一个"字典树(Trie)",然后用数字n与字典中的树一层层进行异或运算————这是search()方法的变异。
举个例子,看上图(数组nums=[2,3,6])
1.构建字典树,假设只有3层。
2.然后遍历数组,假设此时遍历到了6(二进制110)
- 6本身第一层为1,为了得到最大异或数,应该与该层结点0相异或,走0(res+=100)
- 6本身第二层为1,为了得到最大异或数,应该与该层结点0相异或,但第二层没有1,只能走0(res+=000)
- 6本身第三层为0,为了得到最大异或数,应该与该层结点1相异或,走1(res+=001)
- 最终res=101,即5
class Solution {
public int findMaximumXOR(int[] nums) {
Trie trie = new Trie();
for (int n : nums) {
trie.insert(n);
}
int finalRes = 0;
for (int n : nums) {
finalRes = Math.max(finalRes, trie.searchMaxXor(n));
}
return finalRes;
}
}
class Trie {
private TrieNode root = new TrieNode();
// 经典的字典树insert()方法
public void insert(int n) {
TrieNode cur = root;
for (int i = 31; i >= 0; i--) {
int b = (n >> i) & 1;
if (cur.children[b] == null) {
cur.children[b] = new TrieNode();
}
cur = cur.children[b];
}
}
// 变异的字典树search()方法
public int searchMaxXor(int n) {
TrieNode cur = root;
int res = 0;
for (int i = 31; i >= 0; i--) {
int b = (n >> i) & 1;
if (cur.children[1 - b] != null) {
res += (1 << i);
cur = cur.children[1 - b];
} else {
cur = cur.children[b];
}
}
return res;
}
}
class TrieNode {
TrieNode[] children = new TrieNode[2];
}
题目6:异或和相等的相邻子数组
题目描述
给定一个非空整数数组,它的两个紧邻的非空子数组的各自异或和相等。请返回这样的子数组的对数。
输入样例:arr = [2,3,1,6,7]
输出样例:4
这道题目将异或运算的两个性质运用到了极致。
第一个是"自反性":
两个紧邻的非空子数组的各自异或和相等,则这两个紧邻数组的所有元素的异或和为0。直观理解一下:[a,b][c,d]满足题意,则"a^b=c^d",则"a^b^c^d=0"。
第二个是"可移项性":
我们可以轻易得到元素的异或和为0的序列,但是这个整体其实是两个非空数组组成的,那么我们应该从中间的哪里进行分割呢?答案是任何一处。直观理解一下:[a,b][c,d]满足题意,则"a^b=c^d",则"a=c^d^b",其他同理。
class Solution {
public int countTriplets(int[] arr) {
int len = arr.length;
int res = 0;
for (int i = 0; i < len; i++) {
int sum = 0;
for (int j = i; j < len; j++) {
sum ^= arr[j];
if (sum == 0) {
res += (j - i);
}
}
}
return res;
}
}