LeetCode 260. Single Number III 解题报告

LeetCode 260. 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.


示例

Given nums = [1, 2, 1, 3, 2, 5], return [3, 5].


限制条件

  • The order of the result is not important. So in the above example, [5, 3] is also correct.
  • Your algorithm should run in linear runtime complexity. Could you implement it using only constant space complexity?

解题思路

我的思路:

之前做了这道题的简单版本: LeetCode 136. Single Number,里面介绍了找到唯一不重复出现的数字的算法是使用位异或运算,然而这道题中是有两个唯一的数字 A B,所以得到的是 AB 的结果,然而如何使用这个结果得到 A B,我却没有想到办法,所以还是用回之前的那个愚蠢的办法:
使用一个set存储数组中的元素,当元素第一次出现时就放入到set中,当元素第二次出现时就从set中删除该元素,最后set中仅剩下只出现一次的元素,返回这些元素即可。
这种算法是可以通过的,但不是最好的解法。下面是使用位操作运算的最优解法。

参考思路:

之前的博文里讲过异或运算具有以下性质(这里用 表示异或操作):
0N=N
NN=0
所以有以下等式成立:
    0N1N2N3N2N3
=0(N2N2)(N3N3)N1
=0(0)(0)N1
=N1
因此通过将数组里的所有数字都异或一次,我们能得到 AB

问题在于我们不知道 A B,也就没有办法从 AB 中分离出两个数,但是除了 AB ,我们还有一个原始数组呀,那我们是否可以分开原始数组为两个子数组,每个子数组只包含一个唯一出现的数字呢?这样问题就变成了之前的简单问题: LeetCode 136. Single Number,我们也就可以直接用对子数组所有元素异或的方式得到仅出现一次的数字。答案是可以的,方法如下:

由于A跟B是不一样的数,所以 AB0 ,因此 AB 的二进制表示中至少存在一个bit是1。根据异或的定义,这个bit是1,表明了在该位置上, A B对应的bit一个是1,一个是0。
我们可以使用这个bit作为一个标志位,对原来的数组的元素进行划分。假设是第i个bit为1,那么我们设置一个变量bitFlag,bitFlag仅在第i个bit为1,其它bit都为0,用bitFlag与数组中的元素相与,相与结果为0的归为第1组,相与结果不为0的归为第2组,这样 A B肯定分开在这两组中。假如A在该bit上是0,B是1,那么A就会在第1组,B在第2组,反之亦然。 同时,相同的数会分在同一组中,这样实现了之前我们的想法,把复杂的问题转为以前解决过的简单问题。

这里还有一个关键点,那就是bitFlag的设置。当然,你可以用原始的方式,通过移位找到 AB 的某一个为1的bit的位置,然后构建这个bitFlag变量。然而,存在一种很简单的方式设置bitFlag的值,这里选择的是 AB 最右边值为1的bit作为标准位:

int bitFlag = xorSum & (~(xorSum - 1));

乍看之下,这种方式这奇怪,也不直观,所以我详细讲解一下它的原理:
1.在LeetCode 191.Number of 1 Bits一文中我们知道n -1会把n的最低的为1的bit变成0,剩下的bit为1,把这个bit称为第i个bit。如下图

2.当对n-1取反后,第i个bit左边的位都变成相反的值,而第i个bit变回1,第i个bit右边的位都变回0。
如果以上图的n为例,即~(n -1):…001 1 00
3.对n与~(n -1)进行相与,得到的就是仅在第i个位为1,其它位为0的值,因为n和(n-1)的第i个bit左边的所有位都不相同(第二步对n-1取反的结果),第i个bit都为1,第i个bit右边的位均为0,所以n&(~(n -1))能得到我们想要的结果。


代码

我的代码
class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        set<int> single;
        set<int>::iterator itr = single.end();
        vector<int> result;
        for (int i = 0; i < nums.size(); i++) {
            itr = single.find(nums[i]);
            if (itr == single.end()) {
                single.insert(nums[i]);
            } else {
                single.erase(nums[i]);
            }
        }

        for (itr = single.begin(); itr != single.end(); itr++) {
            result.push_back(*itr);
        }

        return result;
    }
};
参考代码
class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        vector<int> single(2, 0);
        int xorSum = 0;

        // Calculate A xor B
        for (int i = 0; i < nums.size(); i++) {
            xorSum ^= nums[i];
        }

        // Find the lowest bit that A different from B
        // set that bit to 1 while other bits are 0
        int bitFlag = xorSum & (~(xorSum - 1));

        for (int i = 0; i < nums.size(); i++) {
            // Numbers that have 0 at that bit
            if ((bitFlag & nums[i]) == 0) {
                single[0] ^= nums[i];
            } 
            // Numbers that have 1 at that bit
            else {
                single[1] ^= nums[i];
            }
        }

        return single;
    }
};

总结

真心感觉做题时方向要对,虽然我知道会用位异或操作,但是我却对着 AB 苦思怎么分解出 A B,结果什么都弄不出来,所以思维得开阔一些,多锻炼各种变种题目,积累经验。
终于填了今天的坑,明天加油!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值