前缀异或

最近,在 leetcode 上做了两道题都是关于前缀异或的:1310. 子数组异或查询1442. 形成两个异或相等数组的三元组数目

前缀异或简介

给定一个数组arr,当需要频繁进行计算 arr 中 给定区间 [i, j] 内所有元素的异或时,直接使用暴力求解往往效率不高,此时可以考虑使用前缀异或

1、前缀异或数组

首先,需要构造一个前缀异或数组 xors,其特点如下:

  1. xors.length = arr.length + 1

    为了统一写法(即,不用单独i = 0 的情况),这里把 xors 长度初始化为 arr.length + 1。如果 xors.lenght == arr.lenght ,计算 [i, j] 内异或时,需要单独考虑 i == 0 情况。

  2. xors[i] = 0

  3. x o r s [ i ] = a r r [ 0 ] ⊕ a r r [ 1 ] ⊕ . . . ⊕ a r r [ i − 1 ] xors[i] = arr[0] \oplus arr[1] \oplus ... \oplus arr[i - 1] xors[i]=arr[0]arr[1]...arr[i1] // arr数组中索引在 [0, i) 区间内所有元素的异或 ⊕ \oplus

具体构造过程,见如下代码:

/* 1、构造前缀异或数组 */
// xors[i] = arr中索引在[0, i) 内所有元素的异或
int[] xors = new int[arr.length + 1]; // 统一写法,避免讨论i=0;
xors[0] = 0; // 0 ^ i = i
for (int i = 1; i < xors.length; i++) {
	xors[i] = xors[i - 1] ^ arr[i - 1];
}

2、求 [i, j] 内元素的异或

令 数组 arr 中 [i, j] 内所有元素的异或为 S i j S_{ij} Sij
即, S i j = a r r [ i ] ⊕ a r r [ i + 1 ] ⊕ . . . ⊕ a r r [ j ] S_{ij} = arr[i] \oplus arr[i + 1] \oplus ... \oplus arr[j] Sij=arr[i]arr[i+1]...arr[j]

由于 x o r s [ i ] = a r r [ 0 ] ⊕ a r r [ 1 ] ⊕ . . . ⊕ a r r [ i − 1 ] xors[i] = arr[0] \oplus arr[1] \oplus ... \oplus arr[i - 1] xors[i]=arr[0]arr[1]...arr[i1]
以及异或的两个性质:

  • i ⊕ i = 0 i \oplus i = 0 ii=0;
  • 0 ⊕ i = i 0 \oplus i = i 0i=i

所以 S i j = a r r [ i ] ⊕ a r r [ i + 1 ] ⊕ . . . ⊕ a r r [ j ] S_{ij} = arr[i] \oplus arr[i + 1] \oplus ... \oplus arr[j] Sij=arr[i]arr[i+1]...arr[j]

即, S i j = ( a r r [ 0 ] ⊕ a r r [ 1 ] ⊕ . . . ⊕ a r r [ i − 1 ] ) ⊕ ( a r r [ 0 ] ⊕ a r r [ 1 ] ⊕ . . . ⊕ a r r [ i − 1 ] ) ⊕ ( a r r [ i ] ⊕ a r r [ i + 1 ] ⊕ . . . ⊕ a r r [ j ] ) S_{ij} = (arr[0] \oplus arr[1] \oplus ... \oplus arr[i - 1] ) \oplus (arr[0] \oplus arr[1] \oplus ... \oplus arr[i - 1] ) \oplus (arr[i] \oplus arr[i + 1] \oplus ... \oplus arr[j]) Sij=(arr[0]arr[1]...arr[i1])(arr[0]arr[1]...arr[i1])(arr[i]arr[i+1]...arr[j])

即, S i j = ( a r r [ 0 ] ⊕ a r r [ 1 ] ⊕ . . . ⊕ a r r [ i − 1 ] ) ⊕ ( a r r [ 0 ] ⊕ a r r [ 1 ] ⊕ . . . ⊕ a r r [ i − 1 ] ⊕ ( a r r [ i ] ⊕ a r r [ i + 1 ] ⊕ . . . ⊕ a r r [ j ] ) ) S_{ij} = (arr[0] \oplus arr[1] \oplus ... \oplus arr[i - 1] ) \oplus (arr[0] \oplus arr[1] \oplus ... \oplus arr[i - 1] \oplus (arr[i] \oplus arr[i + 1] \oplus ... \oplus arr[j])) Sij=(arr[0]arr[1]...arr[i1])(arr[0]arr[1]...arr[i1](arr[i]arr[i+1]...arr[j])) 【异或结合律】

即,$S_{ij} = x o r s [ i ] ⊕ x o r s [ j + 1 ] xors[i] \oplus xors[j + 1] xors[i]xors[j+1]

xors[i] = arr[0, i) 内所有元素的异或,这里是左闭右开区间。 S i j S_{ij} Sij中是左闭右闭区间。

例子

1、1310. 子数组异或查询

有一个正整数数组 arr,现给你一个对应的查询数组 queries,其中 queries[i] = [Li, Ri]。

对于每个查询 i,请你计算从 Li 到 Ri 的 XOR 值(即 arr[Li] xor arr[Li+1] xor … xor arr[Ri])作为本次查询的结果。

并返回一个包含给定查询 queries 所有结果的数组。

 public int[] xorQueries(int[] arr, int[][] queries) {
		// xors[i]表示arr中索引在[0, i) 内所有元素的异或
		int[] xors = new int[arr.length + 1];  /** !!! 注意和solution的区别 */
	    xors[0] = arr[0]; 
		for (int i = 1; i < xors.length; i++) { // 构造前缀异或数组 xors
			xors[i] = xors[i - 1] ^ arr[i - 1]; 
		}
	
		int[] ans = new int[queries.length];
		for (int i = 0; i < queries.length; i++) {
			int l = queries[i][0];  // 左闭右闭区间
			int r = queries[i][1];
			/** !!! 注意和solution的区别 */
			ans[i] =  xors[l] ^ xors[r + 1]; // 统一写法  
		}
		
		return ans;
    }

如果 xors.length = arr.lenght,则此时 xors[i] 表示arr中索引在 [0, i] 内所有元素的异或,此时计算 S i j S_{ij} Sij 时需要单独考虑 i == 0情况。【不推荐这样写,还是初始化 xors.length = arr.lenght + 1,统一写法】

代码如下:

public int[] xorQueries(int[] arr, int[][] queries) {
		// xors[i]表示arr中索引在 [0, i] 内所有元素的异或
		int[] xors = new int[arr.length]; 
		xors[0] = arr[0]; 
		for (int i = 1; i < xors.length; i++) { // 构造前缀异或数组 xors
			xors[i] = xors[i - 1] ^ arr[i]; // 区别1
		}

		int[] ans = new int[queries.length];
		for (int i = 0; i < queries.length; i++) {
			int l = queries[i][0];  // 左闭右闭区间
			int r = queries[i][1];
			if (l == 0) { // 区别2:单独处理l==0
				ans[i] = xors[r];
			} else { // l > 0
			// 区别3
				ans[i] = xors[l - 1] ^ xors[r]; // 用到了异或的结合律、x ^ x == 0
			}
		}
		
		return ans;
    }

2、1442. 形成两个异或相等数组的三元组数目

解法一:三层 for

由于arr.length范围最大是 300,可以三重暴力枚举i、j、k

  1. 构造前缀异或数组xors
  2. 三层 for 暴力枚举i、j、k
  3. 利用前缀异或数组 计算a、b。注意:[左开右闭区间)
    • int a = xors[i] ^ xors[j]; // [i, j) 内所有元素的异或
    • int b = xors[j] ^ xors[k + 1]; // [j, k],即[j, k + 1) 内所有元素的异或
  4. 判断条件优化
    • 原始条件:a == b
    • 即,xors[i] ^ xors[j] == xors[j] ^ xors[k + 1]
    • 也即, xors[i] == xors[k + 1]
public int countTriplets(int[] arr) {
		/* 1、构造前缀异或数组 */
		// xors[i] = [0, i) 内所有元素的异或
		int[] xors = new int[arr.length + 1]; // 统一写法,避免讨论i=0
		xors[0] = 0;
		for (int i = 1; i < xors.length; i++) {
			xors[i] = xors[i - 1] ^ arr[i - 1];
		}
		/* 2、由于arr.length范围最大是 300,可以三重暴力枚举i、j、k */
		// 1)暴力枚举i、j、k 
		int ans = 0;
		for (int i = 0; i < arr.length; i++) {
			for (int j = i + 1; j < arr.length; j++) {
				for (int k = j; k < arr.length; k++) {
					// 2)利用前缀异或数组 计算a、b。注意:[左开右闭区间)
//					int a = xors[i] ^ xors[j]; // [i, j) 内所有元素的异或
//					int b = xors[j] ^ xors[k + 1]; // [j, k],即[j, k + 1) 内所有元素的异或
//					if (a == b) {
//						ans++;
//					}
					/** 
					 * <优化> 
					 * a == b
					 * 即,xors[i] ^ xors[j] == xors[j] ^ xors[k + 1]
					 * 也即, xors[i] == xors[k + 1]
					 * */
					if (xors[i] == xors[k + 1]) {
						ans++;
					}
				}
			}
		}
		
		return ans;
    }

解法二: 两层 for

  • 由于解法一中<优化>后的判断条件为:if (xors[i] == xors[k + 1]) ans++;
  • 因此,当满足 xors[i] == xors[k + 1] 时,j 只要在 [i+1, k] 内都是满足要求的
  • 所以,只用两层枚举 i 和 k即可。即,三重for -> 两层for <两层for>
public int countTriplets(int[] arr) {
		/* 1、构造前缀异或数组 */
		// xors[i] = [0, i) 内所有元素的异或
		int[] xors = new int[arr.length + 1]; // 统一写法,避免讨论i=0
		xors[0] = 0;
		for (int i = 1; i < xors.length; i++) {
			xors[i] = xors[i - 1] ^ arr[i - 1];
		}
		/* 2、e重暴力枚举i、k */
		// 1)暴力枚举i、k
		int ans = 0;
		for (int i = 0; i < arr.length; i++) {
			for (int k = i + 1; k < arr.length; k++) {
				// 2)利用前缀异或数组 计算a、b。注意:[左开右闭区间)
				// int a = xors[i] ^ xors[j]; // [i, j) 内所有元素的异或
				// int b = xors[j] ^ xors[k + 1]; // [j, k],即[j, k + 1) 内所有元素的异或
				// if (a == b) {
				// ans++;
				// }
				/**
				 * <优化> a == b 即,xors[i] ^ xors[j] == xors[j] ^ xors[k + 1] 也即, xors[i] ==
				 * xors[k + 1]
				 */
				if (xors[i] == xors[k + 1]) {
					// ans++;
					// !!! 在此条件下,任意 j ∈ [i + 1, k] 都满足条件
					ans += (k - i);
				}
			}
		}

		return ans;
	}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值