https://blog.csdn.net/qq_35546040/article/details/80284079原博主(仅仅为记录给自己看)
- 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
思路:本来使用简单的一个个数字比较做,后来才知道这是位运算(由于其他元素都出现了两次,因此把全部元素亦或一下,结果就出来了)方面的题,我是菜鸡阿,记得当时我导师面试我的时候也问过我这个问题,emmmm。
//小菜鸡的初始代码(错误示例):时间复杂为O(n^2)
int singleNumber(int* nums, int numsSize) {
int i, j;
int flag = 0;
for(i=0; i<numsSize&&flag==0; i++){
flag = 1;
for(j=0; j<numsSize; j++){
if(i == j)
continue;
if(nums[i] == nums[j]){
flag = 0;
break;
}
}
}
if (flag == 0)
return -1;
else
return nums[i-1];
}
异或(exclusive OR,缩写成xor)是一个数学运算符。它应用于逻辑运算。异或的数学符号为“⊕”,计算机符号为“xor”。其运算法则为:a⊕b = (¬a ∧ b) ∨ (a ∧¬b)
即如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。
同时异或也叫半加运算,其运算法则相当于不带进位的二进制加法:二进制下用1表示真,0表示假,则异或的运算法则为:0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0(同为0,异为1),这些法则与加法是相同的,只是不带进位,所以异或常被认作不进位加法。
// 异或示例:时间复杂为O(n)
int singleNumber(int* nums, int numsSize) {
int result=0;
for(int i=0; i<numsSize; i++)
result = result^nums[i];
return result;
}
- 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
示例 1:
输入: [2,2,1,1,1,2,3]
输出: 3
示例 2:
输入: [4,1,1,2,1,2,2]
输出: 4
思路:我们把数组中所有数字的二进制表示的每一位都加起来。如果某一位的和能被3整除,那么只出现一次的数的二进制表示中对应的那一位是0;否则就是1,即按位计算每一位上1的个数,结果模3为1的那些位就是所求数二进制1所在的位。
// 时间复杂度为O(n)
int singleNumber(int* nums, int numsSize) {
int result = 0;
for(int i=0; i<32; i++){
// 左移动i个位置,相当于1*2**i
int mask = 1<<i;
int count = 0;
for(int j=0; j<numsSize; j++)
// 按位与,计算数组中每个数字每位上1的个数
if((mask&nums[j])!=0)
count++;
// 结果模3为1的那些位就是所求数二进制1所在的位
if(count%3 == 1)
result = mask|result;
}
return result;
}
- 给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。
思路: 这道题主要和1.类似,先将数组中所有元素进行同样的异或才做,但是这里有两个不同的数,所以需要从这两个数的异或值反推得到结果。
整数在计算机中是以补码(源码)的形式存储的(不管基于何种语言),所以我们可以用n&-n( 等价于n & ~(n-1) )得到n的二进制最右边的一个1,得到两个数不同的最低位。这样对于这个为1的位置,肯定可以分辨出这两个数,因为一定有两个数在这个位置1个为1,另一个为0。所以遍历整个数组,和这个数做&操作(以之前求出的不同的最低位为标志将全部数分成两个组),一组为该位上是0的,另一组为该位上是1的。把两组分别组内亦或,就可以得到我们要的两个数。
正整数的原码,反码,补码相同;负整数,反码为原码取反(除符号位),补码为反码加1(除符号为);且对一个整数的补码再求补码,等于该整数自身。
void singleNumber(int* nums, int numsSize, int *singleEnum) {
// int singleEnum[2]={0, 0};
int sum = 0;// 记录两个只出现一次的数的异或
for(int i=0; i<numsSize; i++)
sum = sum^nums[i];
int lowest_dif = sum&(-sum);//得出两个数二进制不同的最低位
for(int i=0; i<numsSize; i++){
if((lowest_dif&nums[i]) == 0)
singleEnum[0] = singleEnum[0]^nums[i];
else
singleEnum[1] = singleEnum[1]^nums[i];
}
}
对于类似的题的其他直观的解法:(1)我们很容易就能从排序的数组中找到只出现一次的数字,但排序需要O(nlogn)时间;(2)我们也可以用一个哈希表来记录数组中每个数字出现的次数,但这个哈希表需要O(n)的空间。对比位运算几乎都是O(n)的复杂度。