我是靠谱客的博主 聪明饼干,最近开发中收集的这篇文章主要介绍【LeetCode】一个整型数组里除两个数字之外,其他数字都出现了两次,请找出这两个只出现一次的数字(数组、分组异或),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章目录

    • (1)题目描述
    • (2)解题思路:分组异或

题目难度:中等

(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),因为数值相同,所以它们的第一个二进制位一定是相同的。这样就同时把两个相同的数划分到同一组了。

  • 为了能够更清晰的看到分组情况,我画了一个表格:
数组二进制序列第一个二进制位
100011
100011
200100
200100
300111
300111
401000
401000
501011
601100
  • 分组结果为:

第一个二进制位为 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】一个整型数组里除两个数字之外,其他数字都出现了两次,请找出这两个只出现一次的数字(数组、分组异或)的全部内容,希望文章能够帮你解决【LeetCode】一个整型数组里除两个数字之外,其他数字都出现了两次,请找出这两个只出现一次的数字(数组、分组异或)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(51)

评论列表共有 0 条评论

立即
投稿
返回
顶部