• 【位运算经典应用】 寻找那个唯一的数


    Single Number


    这一系列有三道题,第一题也是最简单最经典的。

    有一个数组,里面的元素每个都出现了两次,除了一个特殊的,求这个特殊元素。接触过这类题目的coder很快能够脱口而出:直接异或就ok了!的确如此:

    var singleNumber = function(nums) {
      return nums.reduce(function(pre, item) {
        return pre ^ item;
      });
    };
    

    但是为何这样能得到答案?我们假设有个数组[1, 2, 3, 1, 2],很显然我们要找出3这个元素。我们首先将数组元素全部用二进制表示:

    0 1
    1 0
    1 1
    0 1
    1 0
    

    我们从右往左,按位分析。如果数组中所有元素全部都是出现两次,那么每一位上的1的数量之和肯定是2的倍数。我们看从右往左的第一位,1出现了三次,这多出的一次就是3的起作用,从右往左数第二位,1同样出现了三次,同样是因为3的原因。所以我们可以得到该元素的二进制表示为11,也就是3。

    继续思考,我们似乎需要这么一种“加法”运算,使得每位上的 1 bit 数量能够得到累积,但是累积到2了就能清零。幸运的是,^运算符就是我们要找的。

    于是代码也就很好理解了。

    Single Number II###


    这是上题的加强版,数组中每个元素都出现三次,除了一个特殊的,找出这个特殊元素。

    我们以数组[1, 2, 3, 1, 2, 1, 2]举例,将数组元素用二进制表示:

    0 1
    1 0
    1 1
    0 1
    1 0
    0 1
    1 0
    

    如果理解了上题,此题的思路似乎也就呼之欲出了。我们也可以按位运算,计算1 bit的数量,如果每个数字都出现三次,那么每位上的1 bit数量肯定是3的倍数,相反如果不是3的倍数,那么就是那个特殊的数在捣蛋。

    我们似乎需要这么一种“加法”运算,使得每位上的 1 bit 数量能够得到累计,并且累计到了3就自动清零。但是理想是美好的,现实是残酷的,并没有这样一种神奇的运算(三进制?)。

    但是我们可以用一个数“辅助”,因为每一位的1 bit数量统计都是类似的,所以假设正在统计某一位的1 bit数量。我们用a来表示 1 bit 的数量,当 1 bit 的数量为0时,a=0;当数量为1时,a=1;当数量为2时,a=2?非也,位运算只能表示0和1,所以这时我们引进第二个变量b,我们用b=1来代表已经有了2个 1 bit,所以当有两个 1 bit 时,a=0,b=1。数量统计结果逢3化0,所以只有0、1、2三种结果:

    bits数量    a     b
       0        0     0
       1        1     0
       2        0     1
    

    思路也就显而易见了,每次运算我们维护a和b的值,运算到最后即可得到结果:

    var singleNumber = function(nums) {
      var a = 0, b = 0;
      nums.forEach(function(item) {
        b = a & (b ^ item);
        a = b | (a ^ item);
      });
    
      return a;
    };
    

    当然最朴素的做法是按位枚举每一位的 1 bit 的数量。

    Single Number III

    还是一个数组,每个元素出现两次,只有两个特殊的元素出现一次,把这两个特殊的元素找出来。

    两个特殊的元素?这时候直接异或也并没有什么卵用了啊...以数组[1, 1, 2, 2, 3, 3, 4, 5]举例,如何把4和5找出来?一个数组中有两个特殊的数字,不能用异或运算得到结果,如果只有一个了呢?没错,我们可以把4和5根据某一规则分到两个数组中,然后在各自数组中进行异或从而得到结果。

    那么如何分配?我们把4和5用二进制表示出来看看:

    1 0 0
    1 0 1
    

    因为两个数字不相同,所以它们的二进制码肯定有一位是不同的。我们只需找出这一位,然后根据这一位上是0是1,将数组所有元素分到两个新的数组中,这时4和5肯定已经被分到了不同的数组中,而其余两个相同的数字肯定在一个数组中,这时就能分别对两个数组进行异或运算了。

    关于这一位,可以找右数第一个不同位,把两数异或找出右边第一个1即可,而两数的异或其实就是原数组所有元素的异或。

    var singleNumber = function(nums) {
      var tmp = nums.reduce(function(pre, item) {
        return pre ^ item;
      });
    
      var one = tmp & (-tmp); // 取右数第一位1
    
      var a = [], b = [];
    
      nums.forEach(function(item) {
        item & one ? a.push(item) : b.push(item);
      });
    
      
      return [
        a.reduce(function(pre, item) {
          return pre ^ item;
        }), 
    
        b.reduce(function(pre, item) {
          return pre ^ item;
        })
      ];
    };
  • 相关阅读:
    弹跳加载中自定义简单控件实现
    属性动画的监听
    属性动画入门学习
    动画示例
    帧动画代码实现示例
    利用代码定义动画
    利用XMl标签定义动画
    自定义控件的使用方法
    自定义控件之canvas变换和裁剪
    自定义控件之Region区域
  • 原文地址:https://www.cnblogs.com/lessfish/p/4795049.html
Copyright © 2020-2023  润新知