题目:一个数组中有三个数字a、b、c只出现一次,其他数字都出现了两次。请找出三个只出现一次的数字。

分析:在博客http://zhedahht.blog.163.com/blog/static/2541117420071128950682/中我们讨论了如何在一个数组中找出两个只出现一次的数字。在这道题中,如果我们能够找出一个只出现一次的数字,剩下两个只出现一次的数字就很容易找出来了。

如果我们把数组中所有数字都异或起来,那最终的结果(记为x)就是a、b、c三个数字的异或结果(x=a^b^c)。其他出现了两次的数字在异或运算中相互抵消了。

我们可以证明异或的结果x不可能是a、b、c三个互不相同的数字中的任何一个。我们用反证法证明。假设x等于a、b、c中的某一个。比如x等于a,也就是a=a^b^c。因此b^c等于0,即b等于c。这与a、b、c是三个互不相同的三个数相矛盾。

由于x与a、b、c都各不相同,因此x^a、x^b、x^c都不等于0。

我们定义一个函数f(n),它的结果是保留数字n的二进制表示中的最后一位1,而把其他所有位都变成0。比如十进制6表示成二进制是0110,因此f(6)的结果为2(二进制为0010)。f(x^a)、f(x^b)、f(x^c)的结果均不等于0。

接着我们考虑f(x^a)^f(x^b)^f(x^c)的结果。由于对于非0的n,f(n)的结果的二进制表示中只有一个数位是1,因此f(x^a)^f(x^b)^f(x^c)的结果肯定不为0。这是因为对于任意三个非零的数i、j、k,f(i)^f(j)的结果要么为0,要么结果的二进制结果中有两个1。不管是那种情况,f(i)^f(j)都不可能等于f(k),因为f(k)不等于0,并且结果的二进制中只有一位是1。

于是f(x^a)^f(x^b)^f(x^c)的结果的二进制中至少有一位是1。假设最后一位是1的位是第m位。那么x^a、x^b、x^c的结果中,有一个或者三个数字的第m位是1。

接下来我们证明x^a、x^b、x^c的三个结果第m位不可能都是1。还是用反证法证明。如果x^a、x^b、x^c的第m位都是1,那么a、b、c三个数字的第m位和x的第m位都相反,因此a、b、c三个数字的第m位相同。如果a、b、c三个数字的第m位都是0,x=a^b^c结果的第m位是0。由于x和a两个数字的第m位都是0,x^a结果的第m位应该是0。同理可以证明x^b、x^c第m位都是0。这与我们的假设矛盾。如果a、b、c三个数字的第m位都是1,x=a^b^c结果的第m位是1。由于x和a两个数字的第m位都是1,x^a结果的第m位应该是0。同理可以证明x^b、x^c第m位都是0。这还是与我们的假设矛盾。

因此x^a、x^b、x^c三个数字中,只有一个数字的第m位是1。于是我们找到了能够区分a、b、c三个数字的标准。这三个数字中,只有一个数字满足这个标准,而另外两个数字不满足。一旦这个满足标准数字找出来之后,另外两个数字也就可以找出来了。

这种思路的C++代码如下:

void getThreeUnique(vector<int>& numbers, vector<int>& unique)
{if(numbers.size() < 3)return;int xorResult = 0;vector<int>::iterator iter = numbers.begin();for(; iter != numbers.end(); ++iter)xorResult ^= *iter;int flags = 0;for(iter = numbers.begin(); iter != numbers.end(); ++iter)flags ^= lastBitOf1(xorResult ^ *iter);flags = lastBitOf1(flags);// get the first unique numberint first = 0;for(iter = numbers.begin(); iter != numbers.end(); ++iter){if(lastBitOf1(*iter ^ xorResult) == flags)first ^= *iter;}unique.push_back(first);// move the first unique number to the end of arrayfor(iter = numbers.begin(); iter != numbers.end(); ++iter){if(*iter == first){swap(*iter, *(numbers.end() - 1));break;}}// get the second and third unique numbersgetTwoUnique(numbers.begin(), numbers.end() - 1, unique);
}int lastBitOf1(int number)
{return number & ~(number - 1);
}void getTwoUnique(vector<int>::iterator begin, vector<int>::iterator end, vector<int>& unique)
{int xorResult = 0;for(vector<int>::iterator iter = begin; iter != end; ++iter)xorResult ^= *iter;int diff = lastBitOf1(xorResult);int first = 0;int second = 0;for(vector<int>::iterator iter = begin; iter != end; ++iter){if(diff & *iter)first ^= *iter;elsesecond ^= *iter;}unique.push_back(first);unique.push_back(second);
}

上文中getThreeUnique从数组中找出三个只出现一次的数字,而getTwoUnique从数组中找出两个只出现一次的数字。lastBitOf1实现分析中的函数f(n)的功能,它只保留数字n的二进制表示中的最后一位1,而把其他所有位都变成0。

在函数getThreeUnique中,我们通过第一个for循环把a、b、c三个数字异或的结果保存到xorResult中,接着在第二个for循环中求出f(x^a)^f(x^b)^f(x^c)并保存到变量flags中。在语句flags=lastBitOf1(flags)求出f(x^a)^f(x^b)^f(x^c)结果的二进制中最后一位是1的位。并根据这一数位求出第一个只出现一次的数字first。接着把first交换到数组的最后,并在数组的前n-1个数字中求出另外两个只出现一次的数字。

博主何海涛对本博客文章享有著作权。网络转载请注明出处http://zhedahht.blog.163.com/。整理出版物请和作者联系。

程序员面试题精选100题(63)-数组中三个只出现一次的数字[算法]相关推荐

  1. 程序员面试题精选100题(34)-数组中只出现一次的数字[算法]

    题目:一个整型数组里除了两个数字之外,其他的数字都出现了两次.请写程序找出这两个只出现一次的数字.要求时间复杂度是O(n),空间复杂度是O(1). 分析:这是一道很新颖的关于位运算的面试题. 首先我们 ...

  2. 程序员面试题精选100题(47)-数组中出现次数超过一半的数字[算法]

    题目:数组中有一个数字出现的次数超过了数组长度的一半,找出这个数字. 分析:这是一道广为流传的面试题,包括百度.微软和Google在内的多家公司都曾经采用过这个题目.要几十分钟的时间里很好地解答这道题 ...

  3. 程序员面试题精选100题:41-50解题报告

    程序员面试题精选100题(41)-把数组排成最小的数[算法]   题目:输入一个正整数数组,将它们连接起来排成一个数,输出能排出的所有数字中最小的一个.例如输入数组{32,  321},则输出这两个能 ...

  4. 程序员面试题精选100题

    程序员面试题精选100题(01)-把二元查找树转变成排序的双向链表 题目:输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表.要求不能创建任何新的结点,只调整指针的指向. 比如将二元查找树   ...

  5. [程序员面试题精选100题]13.第一个只出现一次的字符

    [题目] 在一个字符串中找到第一个只出现一次的字符.如输入abaccdeff,则输出b. [分析] [代码] /********************************* * 日期:2013- ...

  6. 程序员面试题精选100题(51)-顺时针打印矩阵

    // 程序员面试题精选100题(51)-顺时针打印矩阵.cpp : 定义控制台应用程序的入口点. //#include "stdafx.h" #include <iostre ...

  7. 程序员面试题精选100题:求从1到n的正数中1出现的次数

    // 程序员面试题精选100题(25):求从1到n的正数中1出现的次数 // 如 f(253) = (2!=0) * 100 + 2 * f(99) + (5!=0) * 10 + 5 * f(9) ...

  8. 程序员面试题精选100题:11-40解题报告

    程序员面试题精选100题(11)-求二元查找树的镜像[数据结构]   题目:输入一颗二元查找树,将该树转换为它的镜像,即在转换后的二元查找树中,左子树的结点都大于右子树的结点.用递归和循环两种方法完成 ...

  9. [程序员面试题精选100题]19.反转链表

    题目 输入一个链表的头结点,反转该链表,并返回反转后链表的头结点. 分析 假设经过若干操作,我们已经把结点 pre之前的指针调整完毕,这些结点的next指针都指向前面一个结点.现在我们遍历到结点cur ...

最新文章

  1. package.json字段全解
  2. 《2018-2019全球IPv6支持度白皮书》发布,江北新区IPv6示范区建设正式启动
  3. linux系统运维指南 pdf_linux运维:系统监控命令实践
  4. 华为2018春招笔试题目 字节流解析与长整数相乘
  5. 升级python(2.7-3.6.2)
  6. java坐标代码_java实现计算地理坐标之间的距离
  7. 如何做组间差异检验_买套餐送车险,做维修提供代步车,考拉爱车如何实现差异化竞争?...
  8. python基础和软件测试
  9. 【python】谷歌翻译
  10. 机器学习之邹博笔记1
  11. java application.doevents_Application.DoEvents的用法
  12. js 调用谷歌插件截图跨域的iframe---FireShot
  13. 清华大学2019年“全国优秀中学生信息学冬季体验营”报名通知
  14. maven引用公共包_maven项目引用外部jar包的方法
  15. mysql 五舍六入_四舍六入五成双(适用于MYSQL)(最大支持小数点第9位)
  16. Linux系统常见命令缩写的由来
  17. ECharts 数据可视化插件
  18. ClickHouse查询语句详解
  19. 区块链应该打造国产操作系统
  20. 软考高项——【项目进度管理】

热门文章

  1. 房地产还有最后十年机会 抓紧时间转型
  2. 如何统统扩充loop设备的size(linux loop resize2fs)
  3. jvm性能调优 - 06线上应用部署JVM实战_堆内存预估与设置
  4. 深入理解分布式技术 - 降级和熔断
  5. MySQL - 共享锁和排它锁初探
  6. MyBatis-21MyBatis高级结果映射【一对多映射(2种方式)】
  7. Spring-WebApplicationContext解读
  8. Android常见XML属性解析
  9. python list 查找子列_python – SQLAlchemy查询,其中列包含一个子字符串
  10. 我们为什么必须会git和maven