【每日易题】七夕限定——单身狗问题以及进阶问题位运算法的深入探讨

简介: 【每日易题】七夕限定——单身狗问题以及进阶问题位运算法的深入探讨

一.什么是单身狗问题?

  • 单身狗往往是这样的,在别人都成双成对的时候,只有你一个人孤独寂寞冷(比如博主)。而单身狗问题往往是找出一系列数中落单的那个
  • 对于这种问题往往可以暴力求解,但这样显得不够“优雅”,毕竟我们是“单身贵族”嘛!所以我们这里就介绍一种时间复杂度和空间复杂度都比较低的方法——位运算法

找出一个单身狗

  • 先举出最简单的例子
  • 问题如下
//找出单身狗-版本1
//有一个数组只有一个数组出现一次,其余数字都是成对出现的
//请找出只出现一次的数字
//1 2 3 4 5 1 2 3 4
  • 5就是这里的单身狗`
  • 那我们怎样用位运算法求解呢?
  • 我们知道对于异或来说相同为0,相异为1
  • 而异或又有这样的特性
//a^a=0;
//a^0=a;
  • 同时,异或也满足交换律和结合律,我们就能写出以下代码
int find_single_dog1(int arr[], int sz)
{
  int i = 0;
  int ret = 0;
  for (i = 0; i < sz; i++)
  {
    ret ^= arr[i];
  }
  return ret;
}
int main()
{
  int arr[] = { 1,2,3,4,5,1,2,3,4 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  int dog = find_single_dog1(arr, sz);
  printf("%d\n", dog);
  return 0;
}
  • 什么意思呢?
  • 我们通过一个函数把所有的元素都给异或在一起,我们知道相同元素异或在一起为0,而一个元素异或0等于它本身,这样,就可以得到这个“单身狗”了。

找两条单身狗

  • 从上面这题我们又可以深入一点,如果此时有两个人,但是他们是同性或者是异性但是不适合呢?也就是说有两条单身狗我们又该如何解决呢?
//找出单身狗版本2
//一个数组中只有两个数字是出现一次,其他所有数字都出现了两次。、
//编写一个函数找出这两个只出现一次的数字。
//例如:
//有数组的元素是:1,2,3,4,5,1,2,3,4,6
//只有5和6只出现1次,要找出5和6.
  • 此时的难点在于,我们也可以通过把所有的元素都异或起来的方法找到这两条单身狗异或的值,但是我们怎样通过他俩异或的值找到相应的两个元素呢?
  • 我们可以这样考虑,我们如果能把两条单身狗分到不同的组里,再找出每一个组中的单身狗不就和第一种情况一样了吗?
  • 此时我们分组的条件就应该是两者一定存在差异的地方,哪里能凸显两者的差异呢?
  • 先把代码放这,让大家自己想一下,下面会有详细的解释的
void find_single_dog2(int arr[], int sz, int* pd1, int* pd2)
{
  //1. 所有数字异或在一起
  int ret = 0;//5 ^ 6
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    ret ^= arr[i];
  }
  //2. 计算ret的第几位是1,从而找出不同进行分组
  int pos = 0;
  for (i = 0; i < 32; i++)
  {
    if (((ret >> i) & 1) == 1)
    {
      pos = i;
      break;
    }
  }
  //分组
  //计算数组中元素的第pos为1的异或 
  for (i = 0; i < sz; i++)
  {
    if (((arr[i] >> pos) & 1) == 1)
    {
      *pd1 ^= arr[i];
    }
  }
  //计算数组中元素的第pos为0的异或
  *pd2 = ret ^ *pd1;
}
int main()
{
  int arr[] = { 1,2,3,4,5,1,2,3,4,6 };
  int dog1 = 0;
  int dog2 = 0;
  int sz = sizeof(arr) / sizeof(arr[0]);
  find_single_dog2(arr, sz, &dog1, &dog2);
  printf("%d %d", dog1, dog2);
  return 0;
}
  • 看懂了吗?我来解释一下
  • 我们这个ret是把所有数异或进来的,此时只剩下了两条单身狗异或的值,异或相异为1
    也就是说找到ret二进制位是1的位置是不是就找到两条单身狗的不同之处啦?接下来通过分组就可找出单身狗
  • 注意:相同的元素相应的二进制位一定相同,也就是非单身狗们一定会被分到同一组中,从而保证这组中异或后有且仅有一条单身狗。
  • 这里还有一个能偷懒的地方,我们知道了两条单身狗异或的值,又找出了一条单身狗,通过异或法是不是就能快速找出另一条单身狗了?
a^b^a=b


31a76784838d4a1dba6ce3198f4cd785.png

二.LeetCode单身狗进阶题

  • 题目的链接放在这里
    错误集合
  • 说到底,这个题就是单身狗题目的变种,我们同样是通过异或先找出重复出现的值和丢失的值,然后再对它们进行分组,依次找出即可
int* findErrorNums(int* nums, int numsSize, int* returnSize){
    int ret=0;
    int*returnNums=(int*)malloc(sizeof(int)*2);
    *returnSize=2;
    int i=0;
    //通过把数组中元素和1到n中元素异或起来,找到多余元素以及缺少的元素 (找两条单身狗)
    for(i=0;i<numsSize;i++)
    {
        ret^=(i+1);
        ret^=nums[i];
    }
    int pos=0;
    i=0;
    //异或后不同位二进制为1,找1分组
    while(i<32)
    {
        if(((ret>>i)&1)==1)
        {
            pos=i;
            break;
        }
        i++;
    }
    //找出不同位后分组,只用找一个就行
    int nums1=0;
    int nums2=0;
    for(i=1;i<=numsSize;i++)
    {
        if(((i>>pos)&1)==1)
        {
            nums2^=i;
        }
    }
    for(i=0;i<numsSize;i++)
    {
        if(((nums[i]>>pos)&1)==1)
        {
            nums1^=nums[i];
        }
    }
        nums1^=nums2;//分组后的元素异或1-n找相同元素消掉
        //多的或者少的那个元素,不确定,我们需要判断一下
        int flag=0;//标记
        for(i=0;i<numsSize;i++)
        {
            if(nums[i]==nums1)
            {
                flag=1;
                returnNums[0]=nums1;
                break;
            }
        }
        if(flag==1)//数组中找到了这个元素,为重复元素,另一个为缺少元素
        returnNums[1]=ret^nums1;
        else
        {
            returnNums[1]=nums1;
            returnNums[0]=nums1^ret;
        }
        return returnNums;
}

除了注释中提到的,我这里还想提两点需要注意的地方

与找单身狗不同,这里我们没有成对的元素,因此我们在分组前后都通过异或1到n的数找相同元素从而消掉,剩下的就是我们的单身狗了(有些人可能不理解重复的元素咋消掉啊,重复的元素在ret里就因为相同而消掉了,因此照样能找到这条单身狗哦)

与普通题不同的地方在于,我们这里找出两条单身狗后还有辨别一下哪个是重复元素,哪个是缺少元素,在区分时我们通过flag做了一个标记,flag的值被修改也就是数组存在这个数,说明它就是重复元素啦,另一个自然是缺少元素,反之未被修改就与这种情况相反。

总结

  • 今天的内容到这里就结束了,有关位运算的地方如果不太清楚最后自己画画逻辑图就能弄懂,而有关题目博主建议如果看懂了的话不妨自己动手试试哦!!
  • 好了,如果你有任何疑问欢迎在评论区或者私信我提出,大家下次再见啦!
目录
相关文章
【链表】还不会用C++实现链表?一文教会你各种链表的实现
【链表】还不会用C++实现链表?一文教会你各种链表的实现
474 0
|
SQL NoSQL MySQL
MongoDB 执行计划 & 优化器简介 (上)
最近,由于工作需求去了解一下Query是如何在MongoDB内部进行处理,从而丢给存储引擎的。里面涉及了Query执行计划和优化器的相关代码,MongoDB整体思路设计的干净利落,有些地方深入挖一下其实还是能有些优化点的。本文会涉及一条Query被parse之后一路走到引擎之前,都做了那些事情,分析基于MongoDB v3.4.6代码。由于篇幅过长,文章分为上下两篇,分别介绍执行计划 & 优化器和
3952 0
|
SQL 存储 Oracle
6 张图带你彻底搞懂分布式事务 XA 模式
XA 协议是由 X/Open 组织提出的分布式事务处理规范,主要定义了事务管理器 TM 和局部资源管理器 RM 之间的接口。目前主流的数据库,比如 oracle、DB2 都是支持 XA 协议的。
13794 1
6 张图带你彻底搞懂分布式事务 XA 模式
|
移动开发 算法 调度
【贪心算法】一文让你学会“贪心”(贪心算法详解及经典案例)
贪心算法是一种非常常见的算法,它的简单和高效性使其在实际应用中被广泛使用。 贪心算法的核心思想是在每一步都采取当前状态下最优的选择,而不考虑未来可能产生的影响。虽然贪心算法不能保证总是得到最优解,但在很多情况下,它可以获得很好的结果。 本篇文章将介绍贪心算法的基本概念和一些经典应用,以及如何通过贪心算法来解决一些实际问题。希望通过本文的阅读,读者可以对贪心算法有更加深刻的理解,并能够在实际问题中应用贪心算法来得到更好的解决方案。 让我们暴打贪心算法吧!
6298 0
|
缓存 安全 网络协议
HTTP和HTTPS的区别有哪些?
本文简要总结了 HTTP 和 HTTPS 的区别,从概念、端口、连接方式、使用场景、安全性等多个角度进行了对比。HTTP 是无状态的、无连接的应用层协议,适用于一般性网站和性能要求较高的应用;HTTPS 则通过 SSL/TLS 层提供加密、认证和完整性保护,适用于涉及敏感信息和高安全性的场景。文章还讨论了两者在性能上的差异,包括握手和加密开销、缓存效果以及 HTTP/2 的多路复用技术。最终,根据具体需求选择合适的协议能够更好地平衡安全性和性能。
4468 2
HTTP和HTTPS的区别有哪些?
|
8月前
|
并行计算 PyTorch 算法框架/工具
本地部署DeepSeek模型
要在本地部署DeepSeek模型,需准备Linux(推荐Ubuntu 20.04+)或兼容的Windows/macOS环境,配备NVIDIA GPU(建议RTX 3060+)。安装Python 3.8+、PyTorch/TensorFlow等依赖,并通过官方渠道下载模型文件。配置模型后,编写推理脚本进行测试,可选使用FastAPI服务化部署或Docker容器化。注意资源监控和许可协议。
4749 13
|
存储 边缘计算 开发工具
云计算技术:从基础到实践
【10月更文挑战第4天】云计算技术:从基础到实践
阿里云服务器带宽价格参考:选择1M、3M、5M、10M宽带价格解析
阿里云服务器1M、3M、5M、10M宽带需要多少钱?单说阿里云服务器宽带多少钱,而不确定云服务器实例规格及cpu和内存配置的话,是没办法具体说多少钱的,因为云服务器的价格受很多因素影响。本文将详细解析阿里云服务器在选择1M、3M、5M、10M不同带宽下的价格差异,以供大家参考。
阿里云服务器带宽价格参考:选择1M、3M、5M、10M宽带价格解析
|
前端开发 JavaScript 应用服务中间件
linux安装nginx和前端部署vue项目(实际测试react项目也可以)
本文是一篇详细的教程,介绍了如何在Linux系统上安装和配置nginx,以及如何将打包好的前端项目(如Vue或React)上传和部署到服务器上,包括了常见的错误处理方法。
3464 0
linux安装nginx和前端部署vue项目(实际测试react项目也可以)
|
敏捷开发 前端开发 测试技术
软件开发工作流【详解】(含公司产品研发流程图、大厂研发架构图、大厂研发流程图)
软件开发工作流【详解】(含公司产品研发流程图、大厂研发架构图、大厂研发流程图)
6828 1