leetcode解题中异或法的巧妙应用(go语言实现)

目录

1.问题一

2.问题二

3.问题三


1.问题一

数字1~1000放在含有1001个元素的数组中, 其中只有唯一的一个元素值重复,其他数字均只出现一次。 设计一个算法,将重复元素找出来,要求每个数组元素只能访问一次。 如果不使用辅助存储空间,能否设计一个算法实现?

异或法解决如下:

//FindDupByXOR
//方法三:
//异或法:时间复杂度:O(n),空间复杂度:O(1)
//方法功能:在数组中找唯一重复的元素
//输入参数:arr:数组对象
//返回值:重复元素的值,若无重复元素,返回-1
//以数组{1, 3, 4, 2, 5, 3}为例。
//(1^3^4^2^5^3)^(1^2^3^4^5)=(1^1)^(2^2)^(3^3^3)^(4^4)^(5^5)=0^0^3^0^0=3。
//异或有交换律,结合律,自反性(A^B^B=A)
func FindDupByXOR(arr []int) int {
	if arr == nil {
		return -1
	}

	result := 0
	for _, v := range arr {
		result ^= v
	}
	for i := 1; i <= len(arr); i++ {
		result ^= i
	}

	return result
}

针对该问题,一共有5种解法,还有一个扩展,分别是:

package main

import (
	"fmt"
)

/*
数字1~1000放在含有1001个元素的数组中,
其中只有唯一的一个元素值重复,其他数字均只出现一次。
设计一个算法,将重复元素找出来,要求每个数组元素只能访问一次。
如果不使用辅助存储空间,能否设计一个算法实现?
*/

//FindDupByHas
//解法一:
//Hash法:时间复杂度:O(n),空间复杂度:O(n)
//方法功能:在数组中找唯一重复的元素
//输入参数:arr:数组对象
//返回值:重复元素的值,若无重复元素,返回-1
func FindDupByHas(arr []int) int {
	if arr == nil {
		return -1
	}
	data := map[int]bool{}
	for _, v := range arr {
		if _, ok := data[v]; ok {
			return v
		} else {
			data[v] = true
		}

	}

	return -1

}

//FindDupBySum
//解法二:
//求和法:时间复杂度:O(n),空间复杂度:O(1)
//方法功能:在数组中找唯一重复的元素
//输入参数:arr:数组对象
//返回值:重复元素的值,若无重复元素,返回-1
func FindDupBySum(arr []int) int {
	if arr == nil {
		return -1
	}
	sum := 0
	for _, v := range arr {
		sum += v
	}
	return sum - (((len(arr) + 1) * len(arr)) >> 2)

}

//FindDupByXOR
//方法三:
//异或法:时间复杂度:O(n),空间复杂度:O(1)
//方法功能:在数组中找唯一重复的元素
//输入参数:arr:数组对象
//返回值:重复元素的值,若无重复元素,返回-1
//以数组{1, 3, 4, 2, 5, 3}为例。
//(1^3^4^2^5^3)^(1^2^3^4^5)=(1^1)^(2^2)^(3^3^3)^(4^4)^(5^5)=0^0^3^0^0=3。
//异或有交换律,结合律,自反性(A^B^B=A)
func FindDupByXOR(arr []int) int {
	if arr == nil {
		return -1
	}

	result := 0
	for _, v := range arr {
		result ^= v
	}
	for i := 1; i <= len(arr); i++ {
		result ^= i
	}

	return result
}

//FindDupByMap
//方法四:
//映射法:时间复杂度:O(n),空间复杂度:O(1)
//方法功能:在数组中找唯一重复的元素
//输入参数:arr:数组对象
//返回值:重复元素的值,若无重复元素,返回-1
//思想:
/*
以数组array={1, 3, 4, 3, 5, 2}为例。
从下标0开始遍历数组,
(1)array[0]的值为1,说明没有被遍历过,接下来遍历下标为1的元素,
同时标记已遍历过的元素(变为相反数):array={-1, 3, 4, 3, 5, 2}。
(2)array[1]的值为3,说明没被遍历过,接下来遍历下标为3的元素,
同时标记已遍历过的元素:array={-1,-3, 4, 3, 5, 2}。
(3)array[3]的值为3,说明没被遍历过,接下来遍历下标为3的元素,
同时标记已遍历过的元素:array={-1,-3, 4, -3, 5, 2}。
(4)array[3]的值为-3,说明3已经被遍历过了,找到了重复的元素。

*/
func FindDupByMap(arr []int) int {
	if arr == nil {
		return -1
	}
	len := len(arr)
	index := 0

	for true {
		//arr的值是不会比下标大的
		if arr[index] >= len {
			return -1
		}
		//若遇到index的元素<0,就说明该元素已经被访问过了
		if arr[index] < 0 {
			break
		}

		//该元素第一次被访问,取反标记
		arr[index] = arr[index] * -1
		//下一次遍历arr[index]为下标对应的值
		index = arr[index] * -1

		if index >= len {
			fmt.Println("数组中有非法数字")
			return -1
		}

	}
	return index

}

//FindDupByLoop
//方法五:
//环形相遇法:时间复杂度:O(n),空间复杂度:O(1)
//方法功能:在数组中找唯一重复的元素
//输入参数:arr:数组对象
//返回值:重复元素的值,若无重复元素,返回-1
//思想:
/*
该方法就是采用类似于单链表是否存在环的方法进行问题求解。
“判断单链表是否存在环”是一个非常经典的问题,同时单链表可以采用数组实现,
此时每个元素值作为next指针指向下一个元素。
本题可以转化为“已知一个单链表中存在环,找出环的入口点”这种想法。
该题的关键在于,数组array的大小是n,而元素的范围是[1,n-1],
所以,array[0]不会指向自己,进而不会陷入错误的自循环。
如果元素的范围中包含0,则该题不可直接采用该方法。
*/

func FindDupByLoop(arr []int) int {
	if arr == nil {
		return -1
	}
	slow := 0
	fast := 0

	//找到相遇点
	for ok := true; ok; ok = slow != fast {
		fast = arr[arr[fast]] //fast一次走两步
		slow = arr[fast]      //slow一次走一步
	}
	fast = 0
	//找到入口点
	for ok := true; ok; ok = slow != fast {
		slow = arr[slow]
		fast = arr[fast]
	}
	return fast

}

//FindDup
//扩展
//方法功能:对于一个给定的自然数N,有一个N+M个元素的数组,
//其中存放了小于等于N的所有自然数,求重复出现的自然数序列{X}。
//输入参数:arr:数组对象,num:会出现的重复的元素的个数。
//返回值:重复的元素组成的切片。
func FindDup(arr []int, num int) []int {
	res := []int{}
	if arr == nil {
		return []int{-1}
	}
	len := len(arr)
	index := arr[0]
	for true {
		if arr[index] < 0 {
			num--
			arr[index] = len - num
			res = append(res, index)
		}

		if num == 0 {
			return res
		}

		arr[index] *= -1
		index = arr[index] * -1

	}
	return res

}

func main() {
	arr := []int{1, 3, 4, 2, 5, 3}
	fmt.Println("Hash法")
	fmt.Println(FindDupByHas(arr))

	fmt.Println("求和法")
	fmt.Println(FindDupByHas(arr))

	fmt.Println("异或法")
	fmt.Println(FindDupByHas(arr))

	fmt.Println("映射法")
	fmt.Println(FindDupByHas(arr))

	fmt.Println("环形相遇法")
	fmt.Println(FindDupByHas(arr))

	arr1 := []int{1, 2, 3, 3, 3, 4, 5, 5, 5, 5, 6, 7, 6}
	fmt.Println("扩展")
	fmt.Println(FindDup(arr1, 6))

}

2.问题二

给定一个由n-1个整数组成的未排序的数组序列, 其元素都是1~n中的不同的整数。请写出一个寻找数组序列中缺失整数的线性时间算法。

异或法解决如下:

//方法二:异或法
//方法分析:这种方法的时间复杂度为O(n)
//方法功能:寻找数组序列中缺失的整数
//输入参数:arr:对象数组
//返回值:返回缺失的值是多少
func getNumByXor(arr []int) int {
	if arr == nil {
		return -1
	}

	res := 0 //初始值为0,对结果不影响,因为0与任何数异或都等于该数本身。
	for _, v := range arr {
		res ^= v
	}
	for i := 1; i <= len(arr)+1; i++ {
		res ^= i
	}

	return res

}

针对此问题一,还有另一种解法如下:

package main

import "fmt"

/*
给定一个由n-1个整数组成的未排序的数组序列,
其元素都是1~n中的不同的整数。请写出一个寻找数组序列中缺失整数的线性时间算法。
*/

//方法一:累加求和
//方法分析:这种方法的时间复杂度为O(n)
//方法功能:寻找数组序列中缺失的整数
//输入参数:arr:对象数组
//返回值:返回缺失的值是多少

func getNum(arr []int) int {
	if arr == nil { //若对象数组为空,则返回-1
		return -1
	}
	sum := 0
	for _, v := range arr { //求对象数组的和
		sum += v
	}
	n_sum := ((1 + len(arr) + 1) * (len(arr) + 1)) >> 1 //求1-n的和
	return n_sum - sum

}

//方法二:异或法
//方法分析:这种方法的时间复杂度为O(n)
//方法功能:寻找数组序列中缺失的整数
//输入参数:arr:对象数组
//返回值:返回缺失的值是多少
func getNumByXor(arr []int) int {
	if arr == nil {
		return -1
	}

	res := 0 //初始值为0,对结果不影响,因为0与任何数异或都等于该数本身。
	for _, v := range arr {
		res ^= v
	}
	for i := 1; i <= len(arr)+1; i++ {
		res ^= i
	}

	return res

}

func main() {

	arr := []int{1, 4, 3, 2, 7, 5}
	fmt.Println("方法一:累加求和")
	fmt.Println(getNum(arr))

	fmt.Println("方法二,异或法")
	fmt.Println(getNumByXor(arr))
}

3.问题三

数组中有N+2个数,其中,N个数出现了偶数次, 2个数出现了奇数次(这两个数不相等),找出这两个数。 注意:不需要知道具体位置,只需要找出这两个数。

异或法解决如下:

//方法二:异或法
//方法功能:时间复杂度:O(n),空间复杂度O(1)
//输入参数:arr:对象数组
//返回值:出现次数为奇数的元素祖成的切片

/*
假设这两个出现奇数次的数分别为a与b,根据异或运算的性质,
将二者异或运算的结果记为c,由于a与b不相等,
所以,c的值自然也不会为0,此时只需知道c对应的二进制数中某一个位为1的位数N,
例如,十进制数44可以由二进制0010 1100表示,此时可取N=2或者3,或者5,
然后将c与数组中第N位为1的数进行异或,异或结果就是a,b中一个,
然后用c异或其中一个数,就可以求出另外一个数了。
通过上述方法为什么就能得到问题的解呢?其实很简单,
因为c中第N位为1表示a或b中有一个数的第N位也为1,
假设该数为a,那么,当将c与数组中第N位为1的数x进行异或时,
也就是将x与a外加上其他第N位为1的出现过偶数次的数进行异或,化简即为x与a异或,结果即为a。
c异或a,即将为b
*/

func getNumXor(arr []int) []int {
	if arr == nil {
		return []int{-1, -1}
	}
	res := []int{} //记录结果值
	position := uint(0)
	result := 0
	for _, v := range arr { //计算目标值的异或结果,也就是c
		result ^= v
	}

	tmpresult := result

	//找出异或结果中一个位值为1的位置下标(如1100,位置为1的下标为2或者3)
	for i := result; i&1 == 0; i = i >> 1 {
		position++
	}

	//异或的结果c与所有第position位为1的数异或,结果一定为两个结果中的一个
	for _, v := range arr {
		if (v>>position)&1 == 1 {
			result ^= v
		}
	}

	res = append(res, result)           //result即为其中一个值
	res = append(res, tmpresult^result) //用c与result异或就能得出另一个值

	return res
}

针对此题,还有一种解法如下:

package main

import "fmt"

/*
数组中有N+2个数,其中,N个数出现了偶数次,
2个数出现了奇数次(这两个数不相等),请用O(1)的空间复杂度,找出这两个数。
注意:不需要知道具体位置,只需要找出这两个数。
*/

//方法一:hash法
//方法功能:时间复杂度:O(n),空间复杂度O(n)
//输入参数:arr:对象数组
//返回值:出现次数为奇数的元素祖成的切片
func getNumByHash(arr []int) []int {
	if arr == nil {
		return []int{-1, -1}
	}
	res := []int{}          //存放输出结果
	data := map[int]int{}   //用来记录每个元素出现的次数为奇数还是偶数
	for _, v := range arr { //遍历数组
		if _, ok := data[v]; ok { //若data中出现过,就取反(用异或实现)
			data[v] ^= 1
		} else { //若data中没出现过,就赋值为1
			data[v] = 1
		}
	}

	for _, v := range arr { //找到key值为1的元素,添加到输出结果中
		if data[v] == 1 {
			res = append(res, v)
		}
	}

	return res

}

//方法二:异或法
//方法功能:时间复杂度:O(n),空间复杂度O(1)
//输入参数:arr:对象数组
//返回值:出现次数为奇数的元素祖成的切片

/*
假设这两个出现奇数次的数分别为a与b,根据异或运算的性质,
将二者异或运算的结果记为c,由于a与b不相等,
所以,c的值自然也不会为0,此时只需知道c对应的二进制数中某一个位为1的位数N,
例如,十进制数44可以由二进制0010 1100表示,此时可取N=2或者3,或者5,
然后将c与数组中第N位为1的数进行异或,异或结果就是a,b中一个,
然后用c异或其中一个数,就可以求出另外一个数了。
通过上述方法为什么就能得到问题的解呢?其实很简单,
因为c中第N位为1表示a或b中有一个数的第N位也为1,
假设该数为a,那么,当将c与数组中第N位为1的数x进行异或时,
也就是将x与a外加上其他第N位为1的出现过偶数次的数进行异或,化简即为x与a异或,结果即为a。
c异或a,即将为b
*/

func getNumXor(arr []int) []int {
	if arr == nil {
		return []int{-1, -1}
	}
	res := []int{} //记录结果值
	position := uint(0)
	result := 0
	for _, v := range arr { //计算目标值的异或结果,也就是c
		result ^= v
	}

	tmpresult := result

	//找出异或结果中一个位值为1的位置下标(如1100,位置为1的下标为2或者3)
	for i := result; i&1 == 0; i = i >> 1 {
		position++
	}

	//异或的结果c与所有第position位为1的数异或,结果一定为两个结果中的一个
	for _, v := range arr {
		if (v>>position)&1 == 1 {
			result ^= v
		}
	}

	res = append(res, result)           //result即为其中一个值
	res = append(res, tmpresult^result) //用c与result异或就能得出另一个值

	return res
}

func main() {

	arr := []int{3, 5, 6, 6, 5, 7, 2, 2}
	fmt.Println("方法一:Hash法")
	fmt.Println(getNumByHash(arr))

	fmt.Println("方法二:异或法")
	fmt.Println(getNumXor(arr))

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值