【算法】异或算法找缺失的数-力扣两道经典题目的讲解

【算法】异或算法找缺失的数
leetcode面试题17.04–消失的数字及其变式

先赞后看好习惯 打字不容易,这都是很用心做的,希望得到支持你 大家的点赞和支持对于我来说是一种非常重要的动力 看完之后别忘记关注我哦!️️️

异或算法

【例题】leetcode面试题17.04–消失的数字

在这里插入图片描述
在这里插入图片描述

算法及其原理

异或算法
原理:两个相同的数字互相异或结果是0,任何数异或0是它本身。
所以上面这道题的思路:
1.声明一个整型ret变量记录结果,把nums数组里面所有的数异或在一起,赋给ret。
2.让0-n里面所有的数异或在一起,然后再与ret异或,最后就是得到我们缺失的数字。
因为每个数字再经历两次循环异或之后,出现的数字相当于自己异或了自己,剩下0-N中,nums里面没有出现的数字异或一个0,也就是最后ret就是我们缺失的数字。

代码实现
int missingNumber(int* nums, int numsSize){
    int ret=0;
    for(int i=0;i<numsSize;i++)
    {
        ret^=nums[i];
    }
    for(int i=0;i<=numsSize;i++)
    {
        ret^=i;
    }
    return ret;
}

在这里插入图片描述

以上这道题在leetcode的题库里面,算是比较简单的一道题,当然对于初学的伙伴来说,我们如果抛开时间复杂度一定要是O(N)这个条件的话,我们还可以采取一种思路更简单的方式:排序后再找出来。
排序后,我们直接找哪两个数字之间不是相差1即可。

【变式】leetcode-剑指offer56-I-数组中数字出现的次数

相对于上一题,本题的思路较为复杂
在这里插入图片描述
在这里插入图片描述

题目分析

此题对于上一题,其实如果只是找一个只出现一次的数字,对于我们来说是非常简单的,我们只需要将所有数字异或在一起就可以了。
但是,此题的难点就在于,我们要找的是两个只出现一次的数字。
如果还是按照上面的方式全部异或在一起,我们得到的结果就是那两个单独出现的数字相互异或的结果。

例子:假如nums[]={1,1,2,2,3,3,5,6};
假设我们还是按照上面那样,全部异或在一起,我们得到的结果是5^6,而不是5和6。所以死套上面的方法是不可行的。

但是,如果我们将那两个单独出现的数字分开,分到两个组里面,每个组里面的数字相互异或,我们不就可以得到那两个数字了吗?
所以,我们把问题转化为了:如何将两个只出现一次的数字分开?
我们要知道,两个不同数字做按位与运算(&)的时候得到的结果的二进制位的每一位代表了两个数字每一位是否相同。
可能很多小伙伴并不理解这句话,下面我来解释一下。我们就以上面那个数组里面的5,6来举例。

(32位高位的0省略不写)
5的二进制位:0101
6的二进制位:0110
5和6的异或结果:0011
异或的结果0011可以告诉我们很多信息:
因为异或^运算:位相同得0,相异得1。因此,0011告诉我们,5和6的第一位,第二位不同,第三位,第四位相同。
因次我们可以用5,6的第一位(或者第二位)来分开它们。
也就是说,我们可以将数组分为两组,一组每个数字第一位为1的,一组是每个数字第一位为0的。

算法及其原理

有了上面的铺垫,我们就可以知道我们该题的算法是怎么实现的了。
1.将所有数异或在一起
2.计算异或的结果ret哪一位是1,得到位置pos
3.将数组中第pos位为1的放在一组里面,并且异或在一起,将数组中第pos位位0的放在另一组里面,异或在一起。
这样我们就可以得到两个我们想要的,只出现一次的数字了。

代码实现
int* singleNumbers(int* nums, int numsSize, int* returnSize)
//nums数组首元素地址
//numsSize数组大小
//returnSize返回数组的大小
{
	//将所有数字异或在一起
    int i=0;
    int ret=0;
    for(i=0;i<numsSize;i++)
    {
        ret^=nums[i];
    }
    //找出ret哪一位为1
    int pos=0;//记录位数为1的位置
    for(i=0;i<32;i++)
    {
        if((ret>>i)&1==1)
        {
            pos=i;
            break;
        }
    }
    //按照上面的标准将nums数组进行分组
    int num1=0;//记录第一个数
    int num2=0;//记录第二个数
    for(i=0;i<numsSize;i++)
    {
        if((nums[i]>>pos)&1==1)
        {
            num1^=nums[i];
        }
        else
        {
            num2^=nums[i];
        }
    }
    //题目要求我们的返回的数组要在堆上开辟
    int*retArr=(int*)malloc(sizeof(int)*2);
    retArr[0]=num1;
    retArr[1]=num2;
    *returnSize=2;
    return retArr;
}

在这里插入图片描述

尾声

以上就是今天异或算法的全部内容,希望看完的小伙伴都可以有收获,如果对代码中一些细节的地方还是不明白的话,可以私信我。
在离开之前不要忘记点个赞点个收藏哦!

posted @ 2021-11-13 09:59  背包Yu  阅读(7)  评论(0编辑  收藏  举报  来源