第六十四天学习记录:三条单身狗。数组里大多数都是成对的,只有三个数出现一次,找出这三条狗doge

本文主要是介绍第六十四天学习记录:三条单身狗。数组里大多数都是成对的,只有三个数出现一次,找出这三条狗doge,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

以下前序可不看……

抛出这个问题的原因是在学习C语言视频课程的最后一节课临近结尾时,老师在讲解作业时有一道题是如下图:
在这里插入图片描述

因为时间原因,老师讲得比较急,给出了一个思路。但最终还是处于半懂状态。而在解题过程中,老师还提了一句还有“找出三个数字是出现一次”的情况。

虽然学习完一套视频课程是一件让人愉悦的事情,但是对这一道题的一知半解始终在心中是一个疙瘩。。虽然实际情况C语言还有很多未知的奥秘等待着去探索,外加之前学习的内容存在遗忘的情况。
但本着个人的一个准则……对于所学知识,我可以容忍学会后忘记(后面补起来相对容易),也容忍很多很多没有遇到的问题……但是不能容忍遇到了却没弄明白的问题。
废话不多说,本文通过C语言,将从最简单的一条单身狗开始,由易到难解答问题。

正题开始

为了方便书写,我们尽量采用小于16的数字来举例并且忽略int类型的前28位。

一条单身狗

数组里大多数都是成对的,只有一个数出现一次,找出这一个数字。
这也是最简单的,用异或运算将数组里的所有数字都异或一遍即求出那个只出现一次的数字。
原因是基于异或运算的特性。如果是两个相同的数字异或,其值为0。
在这里插入图片描述
因此,不论这个数组里有多少成双成对的数字,一旦全部异或一遍,最终的值都是0.
再来看异或的另一个特性:
当一个数字和0异或后得到的是这个数字本身。
于是最后0再和这最后一个数字异或后还是得到这个数字本身,因此这个数字就是那个唯一的单身狗,代码如下:

#include <stdio.h>int findoneSingledog(int arr[], int n) {int result = 0;for (int i = 0; i < n; i++) {result ^= arr[i];}return result;
}int main()
{int arr[9] = { 1,1,2,5,2,3,3,4,4};printf("%d\n", findoneSingledog(arr, 9));return 0;
}

输出:5

两条单身狗

首先,引出一个优雅的函数Lowestbit,它的作用是求一个整数最低为1的比特位,并返回带有这唯一一个为1的比特位的值。0返回0。

int Lowestbit(int num)
{return num&(-num);
}

举个例子,3的二进制是0011,最低为1的比特位就是第一位,返回0001. 十进制是1
12的二进制是,1100,最低的比特位是第三位,返回0100. 十进制是4
他们的特点是32位中有且只有1位是1

了解了这个函数之后,再来了解一个事实:
两个不一样的数,他们异或之后,必定会至少有一个位是不一样的。
比如5-二进制0101和6-二进制0110,他们的第一位和第二位不一样。
比如5-二进制0101和7-二进制0111,他们的第二位不一样。
因此,如果将这两个数异或之后得到一个数xorResult。
我们再将xorResult带入刚刚的函数Lowestbit
Lowestbit(xorResult),返回的整型数32位只有1位是1,而这两条单身狗在这一位位置的数必定有一个是1,有一个是0.根据这个特性,可以将数组分为2组。而这两条单身狗也必然会被分开。
比如拿5和7举例。
0101和0111异或后的xorResult=0010,带入函数Lowestbit返回0010,1的位置在第二位。
0101的第二位是0,0111的第二位是1

在将两单身狗分到各自的组后就简单了,用前面“一条单身狗”的方法即可求出。
代码如下:

#include <stdio.h>int Lowestbit(int num)
{return num&(-num);
}void findTwoSingledog(int arr[], int n, int* num1, int* num2)
{int a = 0;int b = 0;int i = 0;int xorResult = 0;for (i = 0; i < n; i++) {xorResult ^= arr[i];}for (i = 0; i < n; i++){if ((arr[i] & Lowestbit(xorResult)) == 0) {a ^= arr[i];}else {b ^= arr[i];}}*num1 = a;*num2 = b;
}int main()
{int a = 0;int b = 0;int arr[10] = { 1,1,6,2,5,2,3,3,4,4 };findTwoSingledog(arr, 10, &a, &b);printf("%d %d\n", a, b);return 0;
}

输出:6,5

三条单身狗

在掌握了一条二条之后,我们迎来了相对前面两种情况更难的一种。
因为两条单身狗还可以通过异位0和1分到2组。而三条狗的话二组也分不下……
这个时候,就需要另辟蹊径了。
如何另辟蹊径呢?我们可以通过某种方式强制性让两条单身狗在一起。
我们可以先随便举个例子:
4-二进制:0100–狗1
6-二进制:0110–狗2
12-二进制:1100–狗3
通过这个个例,我们观察到狗2身上有个特性和狗1,狗3不一样,狗2最低位数为1的位是2,而狗1,狗3都在第三位。那我们将数组中所有的数穿一件“衣服”,这件衣服叫**“Lowestbit”**,这三个数就成了:
Lowestbit(狗1)= 0 1 0 0
Lowestbit(狗2)= 0 0 1 0
Lowestbit(狗3)= 0 1 0 0

该成双成对的穿上Lowestbit后,或许会出现一些相同的情况,但最终,他们还是会一一配对。毕竟只要是偶数个相同的数异或其值都是0.
而此时,Lowestbit(狗1)和Lowestbit(狗3)也成功配对,最后就剩下了:
Lowestbit(狗2)= 0 0 1 0,因此,我们加一个条件限制“0 0 1 0”让所有船上Lowestbit等于0 0 1 0的数聚集在一起。那么狗2将会从狗1和狗3中单独出来,而这一堆聚集的其他数同样可以全部配对成功,剩下狗2
于是狗2被找出来了。
下面就简单了,知道了狗2的值后,我们往这群数字中加入一个虚拟的狗2,那么狗2就和虚拟狗2配对成功,剩下狗1和狗3.要找这俩是不是就可以用前面找“两条单身狗”的方法了。于是3条狗就被找出来了。大功告成?
还没完呢!!!
刚刚说了是随便举个例子。随便不能代表全部。我们还应该考虑一些极端情况。
比如:
2-二进制:0010–狗1
6-二进制:0110–狗2
10-二进制:1010–狗3
这种情况下,即便是穿上Lowestbit,这哥仨的属性都是0010,如果将0010作为限制条件,这哥仨还是会被聚集在一起。
再或者:
1-二进制:0001–狗1
2-二进制:0010–狗2
4-二进制:0100–狗3
这种情况下,即便是穿上Lowestbit,这哥仨的属性都还是不一样。不过这种情况下倒也简单,将穿上Lowestbit的所有数字异或之后得出的值0111再Lowestbit之后得到0001,用0001作为限制条件也能够找出狗1
所以我们还是应该关注第一种极端情况。
造成第一种极端情况的根本原因是什么呢?
是他们三个最低位为1的位数都是一样的。这就意味着这三个数有一个特性,是他们三个必定有一个相同的位都是1。那么就可以知道这种情况下,他们3个异或之后的结果不可能是0.
那这就意味着,如果能够通过某种方式让这3个数异或之后必定为0,就可以保证一定能找出一个属性与另外二个不一样的狗。因为如果3个数异或后是0,就可以说他们三个在相同位数上的二进制只有两种情况,第一种是都为0,第二种是两个为1,一个为0.首先我们排除他们所有位都是0的可能性,那我们就肯定能够通过Lowestbit找出这一个位置,这个位置有两个1,一个0.
那下一步该怎么办?
我们所具备的资源并不多,不如就先将所有的数字异或一遍吧……
异或后得出TdogxorResult,TdogxorResult就相当于是三条狗异或得出的值。

在这里插入图片描述
首先,TdogxorResult如果是0,就很简单。前面说的如果三狗异或是0,必然可以通过前面的方法找出特性不一样的那一条。
那么TdogxorResult如果不为0呢?
我们再用TdogxorResult与狗1,狗2,狗3异或。发现这4个数异或之后就必定是0。
那么我们用三个TdogxorResult和三条狗一共6个数异或之后的结果也是0
这意味着我们又有一件新衣服“TdogxorResult^”
(TdogxorResult^狗1)

(TdogxorResult^狗1)^(TdogxorResult^狗2)^(TdogxorResult^狗3)=0

就是说,我们将所有数字先穿上TdogxorResult^之后,就能保证狗仨必满足有一条特性异禀。
然后再将所有数字套上Lowestbit之后:

Lowestbit(TdogxorResult^狗1)
Lowestbit(TdogxorResult^狗2)
Lowestbit(TdogxorResult^狗3)

这样看上去是不是觉得单身狗的B格瞬间提升了许多。

根据以上的思路,我们就可以写代码了:

#include <stdio.h>int Lowestbit(int num)
{return num&(-num);
}void findTwoSingledog(int arr[], int n, int* num1, int* num2)
{int a = 0;int b = 0;int i = 0;int xorResult = 0;for (i = 0; i < n; i++){xorResult ^= arr[i];}for (i = 0; i < n; i++){if ((arr[i] & Lowestbit(xorResult)) == 0){a ^= arr[i];}else {b ^= arr[i];}}*num1 = a;*num2 = b;
}void findThreeSingledog(int arr[],int newarr[], int n, int* num1, int* num2, int* num3)
{int TdogxorResult = 0;int keynum = 0;int i = 0;int a = 0;//假设a对应num1是第一条狗int b = 0;int c = 0;for (int i = 0; i < n; i++){TdogxorResult ^= arr[i];//得出所有数字异或后的结果}for (int i = 0; i < n; i++) {keynum ^= Lowestbit(TdogxorResult ^arr[i]);//得出关键限值条件}for (int i = 0; i < n; i++){if (Lowestbit(TdogxorResult ^arr[i]) == keynum){a ^= arr[i];//通过限值条件筛选出的一条狗}}newarr[n] = a;//这里懒得用可变数组,这句话的意思可以理解成给原数组加入一个和第一条狗一模一样的虚拟狗//这样newarr就只有2条不能配对的狗了……findTwoSingledog(newarr, 12, &b, &c);*num1 = a;*num2 = b;*num3 = c;
}int main()
{int a = 0;int b = 0;int c = 0;int arr[11] = { 1,1,6,2,5,2,3,3,4,7,4 };int newarr[12] = { 1,1,6,2,5,2,3,3,4,7,4,0 };//这里就懒得用可变数组了……findThreeSingledog(arr, newarr,11, &a, &b, &c);printf("%d %d %d\n", a, b, c);return 0;
}

输出:
在这里插入图片描述
改成极端情况:
int arr[11] = { 1,1,4,4,6,2,3,3,9,10,9 };
在这里插入图片描述
int arr[11] = { 1,10,4,6,6,2,3,3,9,10,9 };
在这里插入图片描述
感谢观看……

这篇关于第六十四天学习记录:三条单身狗。数组里大多数都是成对的,只有三个数出现一次,找出这三条狗doge的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/419087

相关文章

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

关于Spring @Bean 相同加载顺序不同结果不同的问题记录

《关于Spring@Bean相同加载顺序不同结果不同的问题记录》本文主要探讨了在Spring5.1.3.RELEASE版本下,当有两个全注解类定义相同类型的Bean时,由于加载顺序不同,最终生成的... 目录问题说明测试输出1测试输出2@Bean注解的BeanDefiChina编程nition加入时机总结问题说明

将sqlserver数据迁移到mysql的详细步骤记录

《将sqlserver数据迁移到mysql的详细步骤记录》:本文主要介绍将SQLServer数据迁移到MySQL的步骤,包括导出数据、转换数据格式和导入数据,通过示例和工具说明,帮助大家顺利完成... 目录前言一、导出SQL Server 数据二、转换数据格式为mysql兼容格式三、导入数据到MySQL数据

Python判断for循环最后一次的6种方法

《Python判断for循环最后一次的6种方法》在Python中,通常我们不会直接判断for循环是否正在执行最后一次迭代,因为Python的for循环是基于可迭代对象的,它不知道也不关心迭代的内部状态... 目录1.使用enuhttp://www.chinasem.cnmerate()和len()来判断for

关于rpc长连接与短连接的思考记录

《关于rpc长连接与短连接的思考记录》文章总结了RPC项目中长连接和短连接的处理方式,包括RPC和HTTP的长连接与短连接的区别、TCP的保活机制、客户端与服务器的连接模式及其利弊分析,文章强调了在实... 目录rpc项目中的长连接与短连接的思考什么是rpc项目中的长连接和短连接与tcp和http的长连接短

Oracle查询优化之高效实现仅查询前10条记录的方法与实践

《Oracle查询优化之高效实现仅查询前10条记录的方法与实践》:本文主要介绍Oracle查询优化之高效实现仅查询前10条记录的相关资料,包括使用ROWNUM、ROW_NUMBER()函数、FET... 目录1. 使用 ROWNUM 查询2. 使用 ROW_NUMBER() 函数3. 使用 FETCH FI

Python MySQL如何通过Binlog获取变更记录恢复数据

《PythonMySQL如何通过Binlog获取变更记录恢复数据》本文介绍了如何使用Python和pymysqlreplication库通过MySQL的二进制日志(Binlog)获取数据库的变更记录... 目录python mysql通过Binlog获取变更记录恢复数据1.安装pymysqlreplicat

Java 字符数组转字符串的常用方法

《Java字符数组转字符串的常用方法》文章总结了在Java中将字符数组转换为字符串的几种常用方法,包括使用String构造函数、String.valueOf()方法、StringBuilder以及A... 目录1. 使用String构造函数1.1 基本转换方法1.2 注意事项2. 使用String.valu