LeetCode一个整型数组里除两个数字之外,其他数字都出现了两次,请找出这两个只出现一次的数字(剑指 Offer 56 - I. 数组中数字出现的次数) | 数组分组异或
Posted CodeWinter
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode一个整型数组里除两个数字之外,其他数字都出现了两次,请找出这两个只出现一次的数字(剑指 Offer 56 - I. 数组中数字出现的次数) | 数组分组异或相关的知识,希望对你有一定的参考价值。
文章目录
题目难度:中等
(1)题目描述
一个整型数组
nums
里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例1:
输入:nums = [4, 1, 4, 6]
输出:[1, 6] 或 [6, 1]
示例2:
输入:nums = [1, 2, 3, 4, 5, 6, 1, 2, 3, 4]
输出:[5, 6] 或 [6, 5]
LeetCode链接:剑指 Offer 56 - I. 数组中数字出现的次数
(2)解题思路:分组异或
- 先来思考一下这个问题
一个整型数组里出现一次的数只有一个,其他数字都出现了两次,请找出这个只出现一次的数字。
要解决这个问题,利用异或的特性(两数相同异或等于0,0和一个数异或就等于其本身),所以数组中只有一个出现一次的数,那么只需要异或整个数组就可以得到这个只出现一次的数。
- 回到本题,出现一次的数有两个
假设输入:nums = [1, 2, 3, 4, 5, 6, 1, 2, 3, 4],我们需要从中找到 5 和 6
类比前面问题的解法,异或整个数组,得到的是 5 和 6 的异或结果,显然是不行。
那么我们能不能把要找的这两个数字,分成两组,一组放一个,这样分别异或这两组数,就得到了这两个数字。
- 分组的要求
1)两个只出现一次的数必须在不同组
2)两个相同的数必须出现在同一组
比如分为(1 3 1 3 5)和(2 4 2 4 6),这样就很容易找到这两个数字了。
- 如何分组呢?(问题核心)
异或整个数组的结果就是这两个出现一次的数异或的结果,是一定不等于 0 的,那么说明这个异或结果的二进制序列中一定是有 1 的,找到这个 1,我们就可以利用这个来分组
nums = [1, 2, 3, 4, 5, 6, 1, 2, 3, 4],全部异或的结果就是 5 和 6 异或的结果,即 0101 和 0110 异或的结果 0011
0011 中为1 的二进制位表示什么意思呢,分析不难知道,异或相同为 0 ,相异为 1,所以二进制位是 1,就表示 5 和 6 的二进制数在第一、二位上的数是不同的。这就是分组依据。
我们就以第一个为 1 二进制位为分组条件,将数组中第一个二进制位为 1 的数分为一组,第二个二进制位为 0 的数分为另一组,这样就把 5 和 6 成功分到不同的组,而剩下的那些相同的数(1 和 1、2 和 2,3 和 3、4 和 4),因为数值相同,所以它们的第一个二进制位一定是相同的。这样就同时把两个相同的数划分到同一组了。
- 为了能够更清晰的看到分组情况,我画了一个表格:
数组 | 二进制序列 | 第一个二进制位 |
---|---|---|
1 | 0001 | 1 |
1 | 0001 | 1 |
2 | 0010 | 0 |
2 | 0010 | 0 |
3 | 0011 | 1 |
3 | 0011 | 1 |
4 | 0100 | 0 |
4 | 0100 | 0 |
5 | 0101 | 1 |
6 | 0110 | 0 |
- 分组结果为:
第一个二进制位为 1 的组(1,1,3,3,5)
第一个二进制位为 0 的组(2,2,4,4,6)
然后,分别对两组数异或,就得到出现一次的那两个数。
-
时间复杂度:O(N),只遍历了数组两次。
-
空间复杂度:O(1),只需常数的空间存放若干变量。
-
参考代码如下:
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* singleNumbers(int* nums, int numsSize, int* returnSize){
//1、异或整个数组,得到两个只出现一次的数字的异或结果
int ret = 0;
int i = 0;
for(i = 0; i < numsSize; i++)
{
ret ^= nums[i];
}
//2、计算ret的哪一个二进制位为1
int target = 1; //0001
while((target & ret) == 0) //按位与结果为0,说明ret的第一个二进制位为0
{
target = target << 1; //左移一位,继续去找ret中第一个为1的二进制位
}
//循环结束后,target中为1的二进制位在第几位,ret的第一个为1的二进制位就在第几位
//3、根据这一位对所有数字分组
//并分别对每一组的数字进行异或操作,得到这两个数字
int a = 0, b = 0;
for(i = 0; i < numsSize; i++)
{
//按位与结果为0,说明nums[i]的那个二进制位为0
if((target & nums[i]) == 0)
{
a ^= nums[i];
}
else //按位与结果不为0,说明nums[i]的那个二进制位不为0
{
b ^= nums[i];
}
}
//4、动态开辟一个存放这两个数字的数组
int* parr = (int*)malloc(2 * sizeof(int));
parr[0] = a;
parr[1] = b;
*returnSize = 2;
return parr;
}
- 补充:计算 ret 的哪一个二进制位为1,并根据此分组,还可以这样写哦
//2、计算ret哪一个二进制位为1
int pos = 0; //记录ret的第几个二进制位为1
for(i = 0; i < 32; i++)
{
if(((ret >> i) & 1) == 1) //不断左移,和1按位与结果为1,找到首个为1的二进制位
{
pos = i; //ret第pos位为1
break;
}
}
//3、把从高到底的第pos位为1、为0的数进行分组
int a = 0, b = 0;
for(i = 0; i < numsSize; i++)
{
//左移pos位,和1按位与结果为1,说明nums[i]第pos位为1
if(((nums[i] >> pos) & 1) == 1)
{
a ^= nums[i];
}
else
{
b ^= nums[i];
}
}
大家快去动手试一试吧!
以上是关于LeetCode一个整型数组里除两个数字之外,其他数字都出现了两次,请找出这两个只出现一次的数字(剑指 Offer 56 - I. 数组中数字出现的次数) | 数组分组异或的主要内容,如果未能解决你的问题,请参考以下文章
leetcode中等剑指 Offer 56 数组中数字出现的次数