异或运算简介

符号

异或运算即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

异或运算编程python 异或运算代码_异或运算编程python

这里用到了上面未曾用过的一个异或性质———"位独立性"。

数组中的所有数字最大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;
    }
}

 
 
 

异或运算编程python 异或运算代码_算法_02