异或思想的算法题

异或思想的算法题

1.消失的数字

题目链接

数组nums包含从0n的所有整数,但其中缺了一个。请编写代码找出那个缺失的整数。你有办法在O(n)时间内完成吗?

示例 1:

输入:[3,0,1]
输出:2

示例 2:

输入:[9,6,4,2,3,5,7,0,1]
输出:8

采用异或的思想

我们知道 异或的规则是相同为0 不同为1 , 我们让一个数字去和0~numsize之间的数去异或,再让它去跟数组里的值去异或,由于消失的数字只会出现一次,其他数字会出现两次,而出现两次数字对应的二进制位的1 终究会被抵消,因此最后二进制位留下来的1 和 0 就是消失的那个数字的二进制位

代码如下:

c 复制代码
int missingNumber(int* nums, int numsSize)
{
    // 让missing 一开始等于这个数组的元素个数
    int missing = numsSize;

    // 创建循环去异或 0~numSize之间的每一个数
    // 再让其去去异或数组中的每一个数
    for(int i = 0; i<numsSize; i++)
    {
        missing ^= i;
        missing ^= nums[i];
    }
    // 走到这里就说明,missing中存储的二进制数就是消失的数字的二进制数

    return missing;
}

首先,在代码中, missing 初始化为 numsSize,因为我们预设缺失的数字是 numsSize

然后,遍历数组,对于每个索引 i,我们使用异或操作:

  • 第一步,我们将当前遍历到的索引值 i 与 missing 进行异或操作。这会使 missing 中存储的值减少或增加 i,取决于当前遍历到的位置。
  • 第二步,我们将当前位置 i 对应的 nums[i] 与 missing 进行异或操作。由于数组中所有正常的数都会在某个时刻出现两次(一次作为索引,一次作为元素),而那个缺失的数只会被用一次,因此在经历了全部异或操作后,那个缺失的整数就会留在 missing 变量中。

通过以上过程和异或运算的特性,最终得到的 missing 即为缺失的整数值。

我们来看一个难度更高的题目:

2.数组中有两个数字只出现了一次

题目:

只出现一次的数字 III

给你一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。

你必须设计并实现线性时间复杂度 的算法且仅使用常量额外空间来解决此问题

示例 1:

输入:nums = [1,2,1,3,2,5]
输出:[3,5]
解释:[5, 3] 也是有效的答案。

示例 2:

输入:nums = [-1,0]
输出:[-1,0]

示例 3:

输入:nums = [0,1]
输出:[1,0]

分析:

如图所示:

  1. 我们首先让 ret = 0 去异或所有数组里的数,那么出现两次的数就会被抵消
  2. 因此ret的结果就是两个出现一次的数异或的值
  3. 那我们就要想办法把这两个数给分离出来
  4. 那么我们采取去找到ret中 的 1 也就是二进制中 为 1 的地方,因为如果是1就说明两个只出现一次的数的二进制 在这个位上面 一个为0 一个为1
  5. 如上图所示,ret中第M位为1的,就说明x1,x2两个数的第M位一个是1,一个是0
  6. 接下来我们就去数组中分离出x1和x2
  7. 让第M位为1的为一组。第M位为0的为一组,就说明x1和x2在不同的一组中
  8. 那我们就让ret的每一个位都去&上一个1 如果结果是1 就说明ret的该位就是1,那我们就跳出循环。1<<m的意思是让1左移m个位
  1. 当我们找到了为1的位了,我们就要分离。

  2. 上图我们让数组的每一个数进行分离,第M位为1 的数就让x1去异或,第M位是0的就让x2去异或,x1异或完全部的数,第M位为1且出现两次的都会抵消,最后剩下的就是只出现一次且第M位为1的数。x2也是同理,第M位为0的数且出现两次的数会被抵消,最后剩下的x2的值就是只出现一次且第M位为0的数

  3. 有一个需要注意的点,我们需要将(1<<m)改成((unsigned int)1 << m),因为int是有符号类型,最高位32位是符号位,不能比较,但是如果传入的数用到了32位,那么我们的程序就无法完成功能的实现。

代码实现:

c 复制代码
int* singleNumber(int* nums, int numsSize, int* returnSize) 
{
    // 我们要让ret去异或数组中所有的数
    int ret = 0; 
    // 遍历数组
    for(int i = 0; i < numsSize;i++)
    {
        ret ^= nums[i];
    }
    // 此时的ret是只出现一次的两个数异或的结果

    // 我们要去检测第m位出现1  要找到这个m  这样我们后面才能对两个数进行分离
    // 为什么要检测第m位出现1呢,因为如果异或的结果是1 就说明这两个数在这个位上面,一个是1 一个是0
    // 这个不同点就是我们的突破口
    int m = 0;
    while(m < 32) // 32是因为一个int类型是4个字节 32个比特位
    {
        // 为什么要让1强制转化成unsigned int类型呢?
        // int 是有符号类型,最高位(第32为)为符号位,不能比较最高位
        // 若我们只比较前31位 就会导致一部分int类型无法被我们的程序正确运行
        if(ret & ((unsigned int)1<<m)) // 1<<m 就是让1左移m个位
            break; // 如果走进这个循环就说明 第m位就是1 
        else
            m++; // 如果走到这里说明第m位不是1  让m++ 让1继续左移
    }

    // 找到了1 之后就要对原数组里的数进行分离
    // 第m位为1 的数 一组  因为只出现一次的数的其中一个数的第m位也是1 
    // 第m位为0的数  一组 ,因为另外一个只出现一次的数 的第m位是0
    int x1 = 0, x2 = 0;
    for(int i = 0; i < numsSize; i++)
    {
        // 为什么要让1强制转化成unsigned int类型呢?
        // int 是有符号类型,最高位(第32为)为符号位,不能比较最高位
        if(nums[i] & ((unsigned int)1<<m)) // 判断数组下标为i的元素的第m位是否为1
        {
            x1 ^= nums[i]; 
            // 让第m位为1的数全部异或,出现两次的数抵消,剩下的就是只出现一次的并且第m位是1的数
        }
        else
        {
            x2 ^= nums[i];
            //让第m位为0的数全部异或,出现两次的数抵消,x2最后剩下的就是只出现一次且第m位是0的数
        }  
    }

    // 走到这里x1 和x2 就是我们要的只出现一次的数  
    int* retArr = (int*)malloc(sizeof(int) * 2);
    retArr[0] = x1;
    retArr[1] = x2;

    *returnSize = 2;
    return retArr;
}
相关推荐
A懿轩A6 分钟前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
古希腊掌管学习的神7 分钟前
[搜广推]王树森推荐系统——矩阵补充&最近邻查找
python·算法·机器学习·矩阵
云边有个稻草人10 分钟前
【优选算法】—复写零(双指针算法)
笔记·算法·双指针算法
半盏茶香11 分钟前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
忘梓.1 小时前
解锁动态规划的奥秘:从零到精通的创新思维解析(3)
算法·动态规划
️南城丶北离1 小时前
[数据结构]图——C++描述
数据结构··最小生成树·最短路径·aov网络·aoe网络
✿ ༺ ོIT技术༻1 小时前
C++11:新特性&右值引用&移动语义
linux·数据结构·c++
字节高级特工1 小时前
【C++】深入剖析默认成员函数3:拷贝构造函数
c语言·c++
计算机学长大白2 小时前
C中设计不允许继承的类的实现方法是什么?
c语言·开发语言
tinker在coding3 小时前
Coding Caprice - Linked-List 1
算法·leetcode