最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 找数组中两个不同的数字也可以用位运算?|刷题打卡

    正文概述 掘金(灬青芒灬)   2021-03-11   514

    在数组中找不同的数字可以说是一道很基础的题目,寻常来说只需要遍历一次就行,一次不行,那就两次,总是可以的。

    但是如果加上了限制条件呢?

    原题链接:剑指 Offer 56 - I. 数组中数字出现的次数 (提示:本题是剑指Offer系列题目,有会员权限,有可能无法打开链接)

    一、先看题

    一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

    示例:

    示例2:

    提示:

    • 2<=nums.length<=1052 <= nums.length <= 10^52<=nums.length<=105

    二、整理思路

    题目很简单,只有三句话。 老规矩,拿到题目先理清思路。

    第一步:

    确认输入输出:

    输入:一个数组;输出:两个只出现一次的数字组成的数组。

    第二步

    怎么处理输入才能得到输出?

    根据我们平常生活中的思维,一眼扫过去,直接就能找到两个孤零零的数字,可这是数组短的情况下。如果说这个数组很长呢?长到填满A4纸的那种?

    这时候我们就会想到,拿笔来,左手指着一个,右手找到一样的就划掉他们。诶,没错!这种思路在算法中也可以实现,那就是遍历数组,然后找到一样的数的时候,就把他们从数组中删掉,这样最后剩下的不就是两个单身狗了吗?!

    可是,这时候就要看题干了,题干里有这么一句话:要求时间复杂度是O(n),空间复杂度是O(1)。

    这么一来,就不能简单粗暴的用两个遍历嵌套来对比了,所以我们可以选择另外一种方式来处理。

    /**
     * @param {number[]} nums
     * @return {number[]}
     */
    var singleNumbers = function (nums) {
        let res=[]
        for(let i=0;i<nums.length;i++){
            //因为每个数最多出现两次
            //那么只要删除后面出现的那个数
            //就可以避免重复遍历了
            let idx = res.indexOf(nums[i])
            if(idx<0){
                res.splice(idx,1)
            }else{
                res.push(nums[i])
            }
        }
        return res
    }
    

    简单粗暴的遍历法,提交之后,速度垫底。。。 不过这在意料之中,做算法嘛,先解出来才是王道。

    既然这样不行,那么再对他优化一下呢?

    我们知道除了两个数,其他都是重复了两次的,那么我们如果先将他们排序,再进行处理,那岂不是就可以直接跳过删除这个环节,直接跳到下一个不同的数上进行判断了吗?

    这样一来,我们需要处理的数直接减半。

    /**
     * @param {number[]} nums
     * @return {number[]}
     */
    var singleNumbers = function (nums) {
        let res=[]
        let srtNums = nums.sort((a,b)=>{return a-b})
        for(let i=0;i<nums.length;i++){
            if(nums[i]!=nums[i+1]){
                res.push(nums[i])
                //如果已经找到了两个数,剩下的就不用再处理了
                if(res.length>=2){ break}
            }else{
                //因为同样的数排在一起,所以直接跳过
                i++
            }
        }
        return res
    }
    

    经过这一番剪枝和优化,我们的用时不再垫底,来到了三分之一的底部。

    根据以往经验,即使测试用例有所不同,用时的比例也只有10%以内的波动浮动,也就是说,这个算法的优化空间依然巨大!

    那么到底是什么方法呢?苦思很久都没有想到办法,看了题解,不得不说,这真是妙蛙种子回老家,妙到姥姥家了。先上代码,大家可以思考一下:

    /**
     * @param {number[]} nums
     * @return {number[]}
     */
        let res=0
        //取得两个不同的数的异或值
        for(let i=0;i<nums.length;i++){
            res^=nums[i]
        }
        //取得最低的不同位,为了方便理解,在这称mask为辨别码
        let mask=1;
        while((mask&res)==0){
            mask<<=1
        }
        let a=0,b=0;
        for(let i=0;i<nums.length;i++){
            //利用辨别码对数组进行分组
            //从而将找两个数转为找数组中不同的一个数的问题
            if((mask&nums[i])==0){
                a^=nums[i]
            }else{
                b^=nums[i]
            }
        }
        return [a,b]
    }
    

    大家如果有做过那道找出数组中只出现过一次的数的算法题,应该都会对位运算有些印象。

    简单来说,就是两个相同的数进行异或运算之后,会变为0,而0和任意数异或都为他本身,即5^5==0;5^0==5

    根据这个思路,只要我们将数组中的数分为两组,每组只包含一个单独出现的数,那么就可以将其子数组中所有的数进行异或运算,就能得到这个数。

    这个思路的难点,就在于怎么将不同的这两个数,刚好每个子数组分一个呢?

    如果说,大家异或运用比较熟练的话,会想到奇偶数分组的运算,即进行&1操作,即通过判断数字的最后一位二进制是否为1,来进行分组,如果是奇数,那么&1之后必定为1,偶数则为0。

    同样的思路,我们只要通过一个类似于1的功能的数,来对数字进行分组,那么岂不是就能将这两个数刚好分开??

    由于这两个数不同,因此不管怎么样,他们至少有一位二进制不同,我们只要找到最低位的这个二进制数,就可以将所有的数通过&操作来对他们进行分组了。

    至此理清了思路,速度的确快了不少,更重要的是,对位运算的操作又熟悉了一分,不得不感慨位运算的奇特性。

    三、总结

    对于一道看似简单的题目,如果我们要深究下去,可能都还有很大的提升空间,但是优化一般来说都是耗时耗力的,减少损耗的唯一方法就是提高我们对编程基础的掌握,对原理了解的越深,我们才能做得更好。

    一起加油吧!

    找数组中两个不同的数字也可以用位运算?|刷题打卡

    本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情


    起源地下载网 » 找数组中两个不同的数字也可以用位运算?|刷题打卡

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元