一.时间复杂度&异或操作

目录

1.时间复杂度

2.异或操作

位运算的性质:

(1)基本操作

(2)交换&结合

(3)异或运算:相同为0,相异为1

实操:

实现两数交换:

一个数组中有1个出现次数为奇数的数,其余都为出现偶数次的数,请找出这个数:

一个数组中有2个出现次数为奇数的数,其余都为出现偶数次的数,请找出这2个数:


1.时间复杂度

评价一个算法流程的好坏,先看时间复杂度的指标,然后再分析不同数据样本下的实际运行时间

时间复杂度:O(n) -->最坏时间复杂度,是我们一般讨论时间复杂度

#平均时间复杂度 最好时间复杂度一般不讨论

若一个算法的时间复杂度为2n^2+7n+2,我们会省略常数项以及低阶项,采用最高次幂来算,前系数也省略,或者说化为1,即O(n^2)

2.异或操作

        位运算中有& | ^ ~ >> <<几种,我们来聊聊异或运算^的使用。

位运算的性质:

(1)基本操作

        0^N=N

        N^N=0

(2)交换&结合

        a^b=b^a

        a^b^c=a^c^b

(3)异或运算:相同为0,相异为1

        从另一个角度来说,异或运算就是无进位相加

实操:

实现两数交换:

int swap(int a, int b){
	a = a ^ b; #step1
	b = a ^ b; #step2
	a = a ^ b; #step3
}
#a^b^b=a^0=a,step1将a^b赋给了a,step2中将赋给了a的表达式a^b与b异或,得到a^b^b=a,
实现了将a的值给了b,step3等效于a^b^a=a^a^b=0^b=b赋给a,至此完成了两数互换。
当然这个操作有限制,不能在a和b内存地址一样的情况下操作,否则会都变为0.

一个数组中有1个出现次数为奇数的数,其余都为出现偶数次的数,请找出这个数:

思路:根据N^N=0和0^N=N,将数组中所有数全部异或,出现次数为偶数次的数异或都为0,只剩下一堆0异或再异或上唯一一个出现次数为奇数次的数,结果就是我们要找的数。

void Single(int a[]){
	int err = 0;
	for (int i = 0; i < c; i++){
		err ^= a[i];
	}
	printf("%d",err);
}

一个数组中有2个出现次数为奇数的数,其余都为出现偶数次的数,请找出这2个数:

 思路:按着只有一个出现次数为奇数的思路将数组所有数异或,假定这两个数分别为a和b,异或最后结果为err=a^b,因为a不等于b,a^b肯定有一位数为1,a和b中这一位一个为1的话,另外一个肯定为0。我们只需要找出任意位为1的位用rightone(我使用最右为1的位,假定为第8位)来存储,我们用另外一个变量onlyone异或所有除了第8位为0的数,这次异或中排出一位第8位为1的出现次数为奇数次的数,得到的值就是另外一个出现奇数次为奇数次的数b,将onlyone(即b)与err(即a^b)异或b^a^b得到数a

void single(int a[]){
	int err = 0;# 存储所有数异或的结果,即最后化简得到的a^b
	printf("%d\n",c); #c为数组长度用sizeof(a)/sizeof(a[0])计算得来
	for (int i = 0; i < c; i++){
		err ^= a[i];
	}
	int rightone = err & (~err + 1); #这项操作可以求出最右边位为1的数
	int onlyone = 0; #存储数a
	for (int i = 0; i < c; i++){
		if((a[i] & rightone) == 0){ #挑选所有指定位为0的数,作用是剔除另外一个数b
		//if((d & rightone) == rightone){
			onlyone ^= a[i];
		}
	}
	printf("%d %d",onlyone,(onlyone ^ err)); #onlyone存储的就是a,(onlyone^err)为b
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 思路: - 对于一个子数组 A[l...r],它的异或值可以通过前缀异或值的异或运算得到:xor[l-1] xor xor[r]。它的乘积可以通过前缀积的除法运算得到:prod[r] / prod[l-1]。 - 因为异或运算和除法运算都是可逆的,所以 A[l...r] 的乘积等于异或值的充分必要条件是 xor[l-1] xor xor[r] 等于 prod[r] / prod[l-1]。 - 我们可以对于每个 r 计算符合条件的 l 的数量,然后将其加起来即可。 - 对于每个 r,我们需要找到最大的 l,使得 A[l...r] 的乘积等于 A[l...r] 的异或值。我们可以从左往右遍历数组,维护一个乘积 prod 和一个异或值 xor,当 prod 等于 xor 时,我们就找到了一个符合条件的子数组。 - 我们可以使用两个哈希表来优化查找最大的 l 的过程。一个哈希表存储每个前缀的最小的 r,使得 A[i...r] 的乘积等于 A[i...r] 的异或值;另一个哈希表存储每个前缀的最大的 r,使得 A[i...r] 的乘积等于 A[i...r] 的异或值。这两个哈希表可以使用单调栈来维护。 - 时间复杂度为 O(n)。 代码实现: ```go func solve(A []int) int { n := len(A) xor := make([]int, n+1) prod := make([]int, n+1) for i := 1; i <= n; i++ { xor[i] = xor[i-1] ^ A[i-1] prod[i] = prod[i-1] * A[i-1] } cnt := 0 minR := make(map[int]int) maxR := make(map[int]int) stack := make([]int, 0, n) for r := 0; r < n; r++ { for len(stack) > 0 && A[stack[len(stack)-1]] > A[r] { i := stack[len(stack)-1] stack = stack[:len(stack)-1] if len(stack) > 0 { j := stack[len(stack)-1] if prod[r] == prod[j]*A[i] && xor[r] == xor[j] { cnt += r - minR[j] } } if prod[r] == A[i] && xor[r] == 0 { cnt += r - minR[i] } if prod[r] == 0 && xor[r] == xor[i] { cnt += maxR[i] - minR[i] + 1 } delete(minR, i) } stack = append(stack, r) if prod[r] == 0 && xor[r] == 0 { cnt++ } if _, ok := minR[xor[r]]; !ok { minR[xor[r]] = r } maxR[xor[r]] = r } return cnt } ``` 参考文献: - [Codeforces 1426F2 Subsequences (hard version)](https://codeforces.com/problemset/problem/1426/F2) ### 回答2: 假设给定的正整数数组A的长度为n。 要求满足条件的子数组,即满足乘积等于异或的子数组。 首先观察到异或操作具有可逆性,即对任意正整数a和b,满足 a^b=b^a。 在一个子数组内,所有数字异或的结果等于该子数组中所有数字的乘积。因此,我们可以得出结论:一个子数组满足条件,表示该子数组中的数字两两异或的结果等于该子数组中的所有数字的乘积。 假设子数组开始的下标是i,结束的下标是j,则可以表示为 A[i] ^ A[i+1] ^ ... ^ A[j-1] ^ A[j] = A[i] * A[i+1] * ... * A[j-1] * A[j]。 进一步化简得:A[i] * A[i+1] * ... * A[j-1] * A[j] = (A[i] ^ A[i+1] ^ ... ^ A[j-1]) ^ A[j]。 通过上述公式,我们可以利用前缀异或数组prefixXOR[i]表示A[0] ^ A[1] ^ ... ^ A[i]的结果。 因此,我们可以将问题转化为:求出所有满足prefixXOR[i-1] ^ prefixXOR[j] = A[j]的子数组的个数。 具体的解法如下: 1. 初始化一个map,用于存储每个前缀异或结果(prefixXOR[i-1])出现的次数。 2. 初始化计数器count,用于记录满足条件的子数组的个数。 3. 遍历数组A,对于每个位置j,计算prefixXOR[j],然后查找map中是否存在prefixXOR[j] ^ A[j]的值。 - 如果存在,说明存在满足条件的起始位置i,此时将map中对应的值累加到count中。 - 将prefixXOR[j]的值存入map,并将值+1。 4. 返回count,即为满足条件的子数组的个数。 以下为使用go语言编写的实现代码: ```go func numSubarrayProductXOR(A []int) int { prefixXOR := make([]int, len(A)) prefixXOR[0] = A[0] for i := 1; i < len(A); i++ { prefixXOR[i] = prefixXOR[i-1] ^ A[i] } count := 0 prefixMap := make(map[int]int) prefixMap[0] = 1 // 初始化map for j := 0; j < len(A); j++ { if prefixXOR[j] == A[j] { count++ } if val, ok := prefixMap[prefixXOR[j] ^ A[j]]; ok { count += val } prefixMap[prefixXOR[j]]++ } return count } ``` 该算法的时间复杂度为O(n),其中n为数组A的长度。 ### 回答3: 要求时间复杂度小于O(n^2),可以使用滑动窗口的方法来解决这个问题。 首先,定义两个指针left和right,分别指向子数组的起始位置和结束位置。初始化left和right都指向数组的起始位置。 同时,定义一个变量count,用来记录满足要求的子数组的数量。 然后,进入循环,循环条件是right小于数组的长度。在每一次循环中,判断当前子数组的乘积是否等于异或结果。 如果相等,则将count加1,并将right指针向后移动一位。 如果不等,则将left指针向后移动一位。 循环结束后,count的值就是满足要求的子数组的数量。 以下是具体的Go语言代码实现: ```go func countSubArray(arr []int) int { n := len(arr) count := 0 left := 0 right := 0 xor := 0 prod := 1 for right < n { prod *= arr[right] xor ^= arr[right] for left < right && prod != xor { prod /= arr[left] xor ^= arr[left] left++ } if prod == xor { count++ } right++ } return count } ``` 以上代码中,prod表示当前子数组的乘积,xor表示当前子数组的异或结果。每次通过改变left和right的位置来更新prod和xor的值,并在满足条件时将count加1。 该算法的时间复杂度为O(n),符合题目要求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值