Leetcode 260 Single Number III 仅出现一次的数字

原题地址

https://leetcode.com/problems/single-number-iii/

题目描述

Given an array of numbers nums, in which exactly two elements appear only once and all the other elements appear exactly twice. Find the two elements that appear only once.
给出一个数组,其中只有两个元素仅出现一次,其余元素均出现两次。找出只出现一次的两个元素。

For example:
例如:

Given nums = [1, 2, 1, 3, 2, 5], return [3, 5].
给出nums = [1, 2, 1, 3, 2, 5],返回[3, 5]

Note:
注意:

The order of the result is not important. So in the above example, [5, 3] is also correct.
结果的顺序无关紧要。所以上述例子中[5, 3]也是正确的。

Your algorithm should run in linear runtime complexity. Could you implement it using only constant space complexity?
你的算法需要在线性时间复杂度内运行。你可以在常数空间复杂度内实现吗?要求:时间复杂度O(n)空间复杂度O(1)

解题思路

这个题目是SingleNumber系列的第三题,同系列的题目请看如下博文:

  1. Leetcode 136 Single Number
  2. Leetcode 137 Single Number II
  3. 有一个数组data[n-1]存储了1~n中的n-1个数,问data中缺少的数字是多少【每日一题】
  4. Leetcode Single Number 扩展

具体说来,这个题是136题的引申。我们所要考虑的是,如何把这个问题转化为136题中的问题。如果我们简单描述这个题的话,数组是2n+x+y,其中2n是值出现两次的数字,x和y是只出现一次的两个数字,那么我们的问题就是如何将数组分成两部分即2a+x和2b+y其中a+b=n。

网上大神们的思路还是很赞的,对于两个不同的数x和y,求异或x^y=z的结果一定不为0,那么我们根据z即可把x和y分开,同时,我们可以根据z把重复出现2次的n个数也分到两组里,而且一定是成对分开的。这样分开后,就会出现2a+x和2b+y两组数。

算法描述

  1. 求z=x^y
  2. 根据z把所有的数分为两组numsA[], numsB[]
  3. 对numsA[]循环异或求得目标x,对numsB[]循环异或求得目标y

在实际求解中,不必使用numsA和numsB来存储分组结果,直接求异或操作即可,因此只使用常数空间,空间复杂度为O(1)满足要求,时间上需要遍历数组两遍,时间复杂度O(n)也满足要求。

1. z=x^y

对于x^y,我们无法直接求得,因为我们不知道x和y到底是什么(知道的话还求啥)。但是由于源数据中除xy外成对出现,因此可以把所有数字都求异或,由于a^a=0,因此2n个重复的数异或结果为n个0,然后n个0异或结果还是0;再有0^a=a,因此(2n个数求异或)^x^y=(n个0求异或)^x^y==0^x^y=x^y。所以,我们把所有的数字都循环求异或即可得到z=x^y。

2. 分组

上一步得到z之后,如何根据z把所有数字分开呢?由于z是x和y异或所得,所以z中任意一个为1的二进制位都可以把x和y区分开,因为z中为1的二进制位意味者x和y的对应位上一个为0一个为1,我们可以跟据求逻辑与的结果来把x和y区分开。举例:

假设x=0x1,y=0x11
x --- 0x1  --- 00000000 00000000 00000000 00000001
y --- 0x11 --- 00000000 00000000 00000000 00001011
z --- 0x10 --- 00000000 00000000 00000000 00001010

假设我们取z中最后一个为1的位,既倒数第2位,code=2,则
  x    --- 0x1  --- 00000000 00000000 00000000 00000001
  y    --- 0x11 --- 00000000 00000000 00000000 00001011
 code  --- 0x2  --- 00000000 00000000 00000000 00000010
&操作  ----------------------------------------------------------- 结果
x&code --- 0x0  --- 00000000 00000000 00000000 00000000 --- 0 -> false
y&code --- 0x0  --- 00000000 00000000 00000000 00000010 --- 2 -> false  

当然,我们用z中任意一个为1的二进制位都可以把x和y区分开,但是在易操作性上来说,使用最后一个1比较容易一些。

先使用z生成掩码code,然后将源数组中的每一个数字都与code求逻辑与,如果结果为真则放入numsA[],否则放入numsB[],这样就把源数据分为两个数组了,numsA[]中为2a+x,nums[]中为2b+y。

3. 求x和y

接下来就是 Leetcode 136 Single Number 中的问题了,对numsA[]循环求异或得到x,对numsB[]循环求异或得到y

代码

/**
 * 找出仅出现一次的两个数字
 * @param nums 数组,其中只有两个元素仅出现一次,其余元素均出现两次
 * @param numsSize 数组大小
 * @param returnSize 返回数组大小
 * @return 仅出现一次的两个数字
 */
int* singleNumber(int* nums, int numsSize, int* returnSize) {
    int* ret = (int*)malloc(sizeof(int) * 2);
    *returnSize = 2;

    // a: 目标结果a
    // b: 目标结果b
    // tmp: a^b的结果
    // one: a和b最低的不相同的位
    int tmp = 0, a = 0, b = 0, i, one = 1;
    for (i = 0; i < numsSize; ++i) // 计算a^b
        tmp ^= *(nums + i);
        one <<= 1;
        tmp >>= 1;
    }
    // 按a和b最低的不相同的位把所有数据分为两组
    // a和b分别分在两个组里,问题转化为两个2n+1的问题
    // 两组分别异或即可得到两个目标结果
    for (i = 0; i < numsSize; ++i) 
        if (one & *(nums + i))
            a ^= *(nums + i);
        else
            b ^= *(nums + i);
    *ret = a;
    *(ret + 1) = b;
    return ret;
}

测试数据

[1,2,1,3,5,2] : [3,5]

2015/8/18

LeetCode的第260题中,题目被称为“只有一个公共元素的数组 II”。这是一道关于数组操作的数据结构和算法题目。给定两个已经排序的整数数组 nums1 和 nums2,你需要找出它们之间的一个共同的元素,这个元素在每个数组中都只出现一次。 该问题的目标是找到这样的一个公共元素,并返回它的值。例如,如果nums1 = [1, 2, 2, 4] 和 nums2 = [2, 2, 8, 10],那么唯一的公共单次出现的元素是2。 为了解决这个问题,你可以采用一种类似于双指针的方法。从两个数组的起始位置开始遍历,每次比较当前指针对应的元素。如果元素相等且在各自的数组中都是第一次出现(即前一个出现的位置比当前位置大),就将这个元素添加到结果中并继续向后移动指针。如果遇到不同的元素,则移动较小的那个指针的所在数组的指针。 Python 或 C++ 等语言中,可以实现一个循环或者递归的解决方案,直到找到共同的单次出现元素或遍历完其中一个数组。这里是一个简单的 Python 示例: ```python def singleNumber(nums1, nums2): i, j = 0, 0 count = {} while i < len(nums1) and j < len(nums2): if nums1[i] not in count or nums2[j] not in count: count[nums1[i]] = 1 i += 1 elif nums2[j] not in count: count[nums2[j]] = 1 j += 1 else: del count[nums1[i]] del count[nums2[j]] i += 1 j += 1 # 如果有一个数组没遍历完,剩下的最后一个元素就是公共唯一元素 return list(count.keys())[0] if i < len(nums1) else list(count.keys())[-1] ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值