java数组获取第一个_获取数组中只出现一次的数字——通用方法(Java)

题目:在数组中,有一个数字只出现了一次,而其他数字全都出现了N次(N>1),用O(N)的时间复杂度和O(1)的空间复杂度找到那个数字。

1. N = 2

比较经典的题目了,利用a ^ a = 0且异或操作满足交换律、结合律的特性,把数组里所有数字都做一次异或操作,相同的数字会被抵消掉,最后得到的就是只出现一次的那个数字了。

public int findUnique(int[] nums) {

int result = 0;

for (int i = 0; i < nums.length; i++) {

result = result ^ nums[i];

}

return result;

}

变形:N = 2,但只出现一次的数字有两个。

延续上一题的思路,假设数字内容为[a, b, c, c, d, d],那么在对数组里所有数字进行异或之后,得到的结果(假设k)就是我们要找的那两个数字的异或值:k = a ^ b。因为a不等于b,所以k必不为0。

也就是说,k中至少有一位是1,假设是第i位好了,这个第i位的1要么属于a,要么属于b。依据这个规则,可以把数组里的数字也分为两类:①第i位为1的数以及②第i位为0的数,假设a的第i位为1,那么a归属于①类,b归属于②类。

此时,a就是第①类中唯一一个只出现了一次的数字,根据上一题的解法,只要把数组中第i位为1的数字全部做一次异或,就能得到a了。同理,把数组中第i位为0的数字全部做一次异或,就能得到b,但不必这么麻烦,得到a后,只需再做一次异或即可:b = k ^ a。

// 用num1和num2记录结果

public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {

// 这里的two就是要找的两个数字的异或值

int two = 0;

for (int i = 0; i < array.length; i++) {

two = two ^ array[i];

}

// flag是用来判断第i位是否为0的标志

// 通过移动1的位置并与two做与操作来确定flag

int flag = 1;

while ((two & flag) == 0 && flag > 0) {

flag = flag << 1;

}

num1[0] = 0;

num2[0] = 0;

for (int i = 0; i < array.length; i++) {

if ((array[i] & flag) == 0) {

num1[0] = num1[0] ^ array[i];

} else {

// num2[0]可以选择在得到num1[0]后与two做异或得到

num2[0] = num2[0] ^ array[i];

}

}

}

2. N > 2

此时,异或的方法不再有用。考虑到除了目标数字a外,其他数字都出现了N次,如果把这个数字去掉,那么二进制表示中每一位上的1都会出现N的倍数次。如果加上目标数字,那么如果哪一位上的1的出现次数不是N的整数倍,说明a在该位上是1,否则就是0。

// 此处n为其他数字出现的次数

public int findUnique(int[] nums, int n) {

// Java中的整型为32位

int[] count = new int[32];

// 统计各位上1出现的次数

// 为了方便,这里count[0]表示的是最低位(最右边),顺序与平常的二进制相反

for (int i = 0; i < nums.length; i++) {

int cur = nums[i];

int index = 0;

while (cur != 0) {

if ((cur & 1) != 0) {

count[index]++;

}

cur = cur >>> 1;

index++;

}

}

int result = 0;

// 因为count是从低位开始计的,要想从高位开始计算,需要从后往前遍历数组

for (int i = count.length - 1; i >= 0; i--) {

// 将上一个最低位编程倒数第二位,空出最右边的位置来

result = result << 1;

// 次数不为n的整数倍,说明该位是1,直接加1

if (count[i] % n != 0) {

result = result + 1;

}

}

return result;

}

该方法在n为2时也适用,但相比直接异或还是慢了不少。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值