异或思想的算法题

异或思想的算法题

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;
}
相关推荐
Y40900115 分钟前
C语言转Java语言,相同与相异之处
java·c语言·开发语言·笔记
YuTaoShao16 分钟前
【LeetCode 热题 100】994. 腐烂的橘子——BFS
java·linux·算法·leetcode·宽度优先
Wendy14418 小时前
【线性回归(最小二乘法MSE)】——机器学习
算法·机器学习·线性回归
拾光拾趣录8 小时前
括号生成算法
前端·算法
棐木8 小时前
【C语言】动态内存管理
c语言·free·malloc·realloc·calloc·动态内存
渣呵9 小时前
求不重叠区间总和最大值
算法
拾光拾趣录9 小时前
链表合并:双指针与递归
前端·javascript·算法
好易学·数据结构9 小时前
可视化图解算法56:岛屿数量
数据结构·算法·leetcode·力扣·回溯·牛客网
香蕉可乐荷包蛋10 小时前
AI算法之图像识别与分类
人工智能·学习·算法
chuxinweihui10 小时前
stack,queue,priority_queue的模拟实现及常用接口
算法