异或思想的算法题

异或思想的算法题

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;
}
相关推荐
你怎么知道我是队长1 天前
C语言---未定义行为
java·c语言·开发语言
北京地铁1号线1 天前
2.3 相似度算法详解:Cosine Similarity 与 Euclidean Distance
算法·余弦相似度
Remember_9931 天前
【LeetCode精选算法】滑动窗口专题一
java·数据结构·算法·leetcode·哈希算法
Lueeee.1 天前
v4l2驱动开发
数据结构·驱动开发·b树
小饼干超人1 天前
详解向量数据库中的PQ算法(Product Quantization)
人工智能·算法·机器学习
你撅嘴真丑1 天前
第四章 函数与递归
算法·uva
漫随流水1 天前
leetcode回溯算法(77.组合)
数据结构·算法·leetcode·回溯算法
玄冥剑尊1 天前
动态规划入门
算法·动态规划·代理模式
mjhcsp1 天前
P14987 全等(mjhcsp)
算法·题解·洛谷
(❁´◡`❁)Jimmy(❁´◡`❁)1 天前
Atcoder abc441A~F 题解
算法·深度优先·图论