这里是题目描述:剑指Offer-面试题56-I:组中数字出现的次数
对于这道题,我们很容易就能想到使用哈希表统计数组中每个不同数字出现次数的方法,但是它不满足题目要求的空间复杂度**O(1)**的限制。要满足常数空间开销,我们可以使用位运算中的 异或运算 。
异或运算介绍
在java中,异或位运算的操作符为^
,如1与0异或运算,写作 1^0
。异或运算则是对于两个操作数相互对应的每一位,如果两个操作数这一位相等(都为0
或都为1
),则运算结果的该位是1
否则是0
。如下表:
例如5^3
用二进制表示为101^011
,则结果的二进制为110
,就是十进制的6
异或运算有一个重要的特性:任何数和0进行异或的结果都等于它自身,我们通过分析异或的基本定义可以很容易证明。同时它的逆表述:任何数和它自身进行异或的结果都等于0 也成立
接下来,我们使用这两条特性解决本题
我们使用0
和给定数组nums
中的所有值进行异或运算,那么得到0^nums[0]^nums[1]...^nums[n-1]^nums[n]
。nums
中有两个只出现一次的数字m1
、m2
,那么异或结果中其他所有出现两遍的数字都和自己发生过异或成为0
,因此异或的结果为m1^m2
得到m1^m2
后,我们要根据它将m1
和m2
求出来。因为m1
和m2
不相同,因此它们的二进制表示肯定有不同值的位,这样的位在m1^m2
中值为1
。因此我们从最低位开始向最高位寻找m1^m2
中最低的值为1
的位,寻找方法可以是和初始值为1
的数字进行&
运算,当&
运算结果不为0
,则说明该位不为0
;否则数字1
左移一位再进行&
运算,相当于从低到高寻找不为0
的位
找到m1^m2
第一个不为0
的位后,我们可以根据这一位是否为1
将nums
中的数字分成两个部分,那么两个只出现一次的数字m1
和m2
肯定不位于同一部分,而其他出现两次的数字肯定位于同一部分。再分别用0
和这两部分的数字进行异或运算,就会最终得到m1
和m2
题解代码
class Solution {
//使用位运算中的异或运算
//异或运算特性:1.任何数和它自身异或得0 2.任何数和0异或得它自身
public int[] singleNumbers(int[] nums) {
if(nums.length==2)
{
return nums;
}
int temp=0;
for(int i=0;i<nums.length;i++) //根据异或的性质,temp和nums所有元素异或运算,nums中唯二的出现一次元素n1、n2,temp=n1^n2
{
temp^=nums[i];
}
int the1loc=1;
while((temp&the1loc)==0) //确定temp最低的1的位,也是n1和n2最低的不同位
{
the1loc=the1loc<<1; //左移一位
}
int n1=0,n2=0;
for(int i=0;i<nums.length;i++)
{
if((nums[i]&the1loc)==0)
{
n1^=nums[i];
}
else
{
n2^=nums[i];
}
}
return new int[] {n1,n2};
}
}
时间复杂度:O(n)
空间复杂度:O(1)