找出数组中只出现一次的数字

1.题目(困难)

给定一个数组,除了一个元素只出现1次,其它元素都出现3次。设计一个算法找出只出现一次的元素。要求时间复杂度为O(n),额外空间O(1)。

1.1 例子

输入: arr[] = {12, 1, 12, 3, 12, 1, 1, 2, 3,3}

输出: 2

除了2其它都出现3次。

输入: arr[] = {10, 20, 10, 30, 10, 30, 30}

输出: 20

除了20其它都出现3次。

1.2 java代码实现

class GFG {
	static int getSingle(int arr[], int n)
	{
		int ones = 0, twos = 0;
		int common_bit_mask;

		for (int i = 0; i < n; i++) {
			twos = twos | (ones & arr[i]);
			ones = ones ^ arr[i];
      
			common_bit_mask = ~(ones & twos);
      
			ones &= common_bit_mask;
			twos &= common_bit_mask;
		}
		return ones;
	}

	public static void main(String args[])
	{
		int arr[] = { 3, 3, 2, 3 };
		int n = arr.length;
		System.out.println("只出现一次的元素是 " + getSingle(arr, n));
	}
}

输出:

只出现一次的元素是 2

2.算法讲解

2.1 按位操作

2.1.1 与(&)运算符:

  1. a & b只有a、b同时为1,则a & b = 1。1 & 1 = 1、1 & 0 = 0、0 & 1 = 0、0 & 0 = 0。
  2. 作用:查询,找相同。寻找两个数字共同为1的bit位。

2.1.2 或(|)运算符:

  1. a | b a、b任何一个为1,则a | b = 1。1 | 1 = 1、1 | 0 = 1、0 | 1 = 1、0 | 0 = 0。
  2. 作用:赋值。将a上与b为1的bit位对应的bit位置为1。

2.1.3 非(~)运算符

  1. 取反操作,~1 = 0,~0 = 1。
  2. 作用:结合&操作 清除。a &= ~b。

2.1.4 异或(^)

  1. a ^ b 只有ab互斥,则a ^ b = 1。1 ^ 0 = 1、0 ^ 1 = 1、0 ^ 0 = 0、1 ^ 1 = 0。
  2. 作用:查询,找不同。
  3. 速记:阴阳互补,只有男人和女人才能生出小孩。连连看游戏中相同图案可以抵消掉。

2.2 解题思路

记录每个bit为1的次数。出现1次存在ones上。出现2次存在twos上。出现3次清除掉。

  1. 由于要求时间复杂度为O(n),所以循环遍历数组。
  2. 设置两个变量 ones,twos。ones含义是,元素bit位上值为1出现1次,赋值给ones。ones = ones^arr[i]。 twos的含义是:如果当前遍历的元素bit位上值为1出现2次时,赋值给twos。
  3. 如果当前遍历的元素bit位上值为1出现了3次,则将ones和twos该bit位上的值清零。
  4. 最终ones的值就是只出现一次的数字。

几个疑惑?

  1. 为什么ones要用异或(^)操作?

答:以 { 3, 3, 2, 3 }为例。第一次遍历,ones = 3。*第二次遍历,arr[1] =3。 ones需要变成0。需要抵消掉arr[1]。有点类似消消乐的意思。所以用异或操作。

  1. 如果判断一个元素哪些位上值为1出现了三次?

答:ones & twos。

演算

以{ 3, 3, 2, 3 }为例

  1. 第一次循环 arr[0] = 3 。

twos = tows|(ones&arr[0])

twos = 00000000|(00000000&00000011) =00000000

ones = ones^arr[0]

ones = 00000000^00000011 = 00000011

common_bit_mask = ~(ones & twos)

common_bit_mask = ~(00000011 & 00000000) =11111111

ones & = common_bit_mask
ones = 00000011

twos & = common_bit_mask
twos = 00000000

ones = 3,twos = 0

  1. 第二次循环 arr[1] = 3 。

twos = tows|(ones&arr[1])

twos = 00000000|(00000011&00000011) =00000011

ones = ones^arr[1]

ones = 00000011^00000011 = 00000000

common_bit_mask = ~(ones & twos)

common_bit_mask = ~(00000000 & 00000011) =11111111

ones & = common_bit_mask
ones = 00000000

twos & = common_bit_mask
twos = 00000011

ones = 0,twos = 3

  1. 第三次循环 arr[2] = 2 。

twos = tows|(ones&arr[2])

twos = 00000011|(00000000&00000010) =00000011

ones = ones^arr[2]

ones = 00000000^00000010 = 00000010

common_bit_mask = ~(ones & twos)

common_bit_mask = ~(00000010 & 00000011) =11111101

ones & = common_bit_mask
ones = 00000010 & 11111101 = 00000000

twos & = common_bit_mask
twos = 00000011 & 11111101 = 00000001

ones = 0,twos = 1

  1. 第四次循环 arr[3] = 3 。

twos = tows|(ones&arr[3])

twos = 00000001|(00000000&00000011) =00000001

ones = ones^arr[3]

ones = 00000000^00000011 = 00000011

common_bit_mask = ~(ones & twos)

common_bit_mask = ~(00000011 & 00000001) =11111110

ones & = common_bit_mask
ones = 00000011 & 11111110 = 00000010

twos & = common_bit_mask
twos = 00000001 & 11111110 = 00000000

ones = 2,twos = 0

欢迎入群

我建了一个技术交流群。请加我好友,备注"加群"。我拉你进群

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值