您的位置:首页 > Web前端

剑指offer-数组中只出现1次的数字

2018-03-31 15:24 309 查看

题目描述

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

地址: 牛客链接

问题分析

首先,若允许用额外空间的话,那么可以遍历数组,用set存储,如果当前元素,set中已经存在了,那么将该元素从set中移除;如果set中不存在当前元素,那么将之装入set中。最终,偶数次的元素全部移除,set中只有奇数次的元素。即为所求

实际上,该题考察的不用额外空间,考察位运算的知识。

若将这个数组求异或和,那么出现偶数次的元素相异或的结果为0,所以,最终的异或和 eor = a^b (假如只有a,b出现了奇数次)。那么对于eor 而言,必然至少有一位不为0,并且在那个位置上,a要是为1,b肯定为0;b要是为1,a肯定为0。

所以,如果我们对于 eor 从右往左找到第一个不为0的位置,那么便可以区分a,b。 举例说明: a = 1110, b = 0100,那么 a^b = 1010。在从右往左数第2位上,xor不为0 ,a为1,b为0,这样便可以区分 a,b。

问题又来了,如何得到一个二进制形式的最右边的1呢?

遍历,与 1 << x 相与,直至结果不为0

eor & (-eor)

eor & (~eor + 1)(其实道理和上一个一样,因为位运算是对补码进行的,一个负数的补码,便是他对应正数的补码取反,加1)

举例,对于a = 1110, b = 0100,那么 a^b = 1010。 eor & (~eor + 1) 的结果便是 0010。

利用rightOne 便可以区分出 a 和 b,所以再次遍历数组,只有与 rightOne相与结果为1的才进行异或和,这样便淘汰了 a 或者 b 中的一个,而最终的异或肯定也是 a 或者 b。那么将 该异或和与 eor 相异或,那么结果是 两者中的另一个。 理由: a ^ b = c,那么 c ^ a = b

经验教训

异或运算的性质(奇偶个数异或和性质)

如何得到一个二进制形式的最右边的1呢: eor & (-eor) 或者 eor & (~eor + 1)

该题可以引申出三个问题

如果一个数组中,只有一个数字出现了奇数次,其他都出现了偶数次,那么如何得到该奇数次的数字

求该数字的异或和,异或和即为所求

如果一个数组中,有两个数字出现了奇数次,其他都出现了偶数次,那么如何得到该奇数次的数字

方法同本题一样

若数组中有一个数出现了一次,剩下的数出现了K次,如何求出出现了 1 次的数字

将所有的数都转化为K进制形式,然后将所有K进制形式每一位相加,最后将每一位 对K取模,将每一位取模后的结果转换为十进制,即为所求。

举例,7出现1次,4出现3次,那么 7的三进制形式为 21,4的三进制形式为 11。每一位累加结果为 2*1 + 1* 3 =5, 1*1 + 1*3 = 4。取模结果为 21,转为十进制 7

相应位运算的题:

剑指offer-二进制中1的个数

剑指offer-数值的整数次方

代码实现

//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
public class Solution {
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
if (array == null || array.length == 0) {
return;
}
int eor = 0;
for (int i = 0; i < array.length; i++) {
eor ^= array[i];
}
//int rightOne = eor & (~eor + 1);
int rightOne = eor & (-eor);
int firstValue = 0;
for (int i = 0; i < array.length; i++) {
if ((rightOne & array[i]) != 0) {
firstValue ^= array[i];
}
}
num1[0] = firstValue;
num2[0] = firstValue ^ eor;
return;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息