一篇搞定JS的位运算(公式+力扣真题)--- 持续更新

摘要

位操作(Bit Manipulation)是程序设计中对位模式或二进制数的一元和二元操作。在许多古老的微处理器上,位运算比加减运算略快,通常位运算比乘除法运算要快很多。在现代编程语言中,情况并非如此,很多编程语言的解释器都会基本的运算进行了优化,因此我们在实际开发中可以不必做一些编译器已经帮我们做好的优化,而就写出代码本身所要表现的意思。

在对位运算的学习过程中,可以锻炼写代码的思路,同时在面试过程中,如果使用位运算来对问题进行解决,也会很加分。

本篇文章从位运算的公式,再到一些比较经典的位运算题目进行讲解。从而帮助大家更好的掌握位运算。该文章下的所有题目都可以在leetcode中搜索到。学会了之后可以自己进行尝试。

1.位运算符

我们定义两个二进制数 A:1010(10) ,B:1101(15)

运算符 描述 结果
& 所有位按位进行与操作 A & B :1000
| 所有位按位进行或操作 A | B : 1111
>> 无符号右移 A >> 1 : 0100
<< 无符号左移 A<<1 : 10000
>>> 有符号右移 A >>> 1 : 0100
^ 所有位按位进行异或操作 A ^ B : 0111
~ 所有位按位进行取反操作 ~A : 0101

2.>> 和 >>>的区别

现在说第一个问题,有符号右移和无符号右移有什么区别呢?
从上面的例子来看,似乎结果是一样的,为了解释这个问题,首先要解释一下在JS中,位运算的操作一共为32位,而第一位为符号位。


负数的二级制为对应正数的所有位取反加一

也就是说,对于2和-2来讲,他们的二进制为:

2: 00000000000000000000000000000010

-2:1111111111111111111111111111111111110

在针对负数进行操作的时候:

  • 如果是无符号右移 >>,头部补1。所以就变成了:
    111111111111111111111111111111111111
    对应十进制的-1

  • 如果是有符号右移 >>>,头部补0。就变成了:
    0111111111111111111111111111111111111
    对应十进制的2147483647
    所以使用>>>得到的一定是正数。

这里有一个比较常见的技巧:

<< 1 是×2的意思
>>1 是÷2的意思,但是位运算不能处理小数。
>>0 可以去掉小数点转换成整数

3.位运算公式

公式 结果
变为相反数 - x ~(x - 1) 或者 ~x + 1
x & -x 返回 x 的最后一位1
x >> k & 1 求 x 的第k位数字
x | (1 << k ) 将 x 第k位数字置为1
x ^ (1 << k) 将 x 第k位数字取反
x & (x - 1) 将x最右边的1置为0(去掉最右边的1)
x | (x + 1) 将x最右边的0置为1
x & 1 判断奇偶性

上面的公式,最好能够自己通过一些例子进行演示出来。然后能做到碰到对应的情况下,直接想到。如果不能的话也要大概知道使用什么运算符,再自己进行推算出来。

因为后面要练习的题目,都要依靠于上面的公式技巧。所以这些公式还是需要掌握完全的,同时也要对位运算符的使用比较熟练。

4.经典题目

4.1 力扣67 二级制求和

给你两个二进制字符串 a 和 b ,以二进制字符串的形式返回它们的和。

输入:a = “11”, b = “1”
输出:“100”

输入:a = “1010”, b = “1011”
输出:“10101”

对于该题,很容易能想到的方法是,将字符串的二级制数转成十进制,然后进行相加,得到的结果再转成二进制。
但是这不是我们做这道题的初衷,我们可以通过模拟二进制加法的方式来对该题解答:

方法1:

从最低位进行相加,用一个变量来保存当前的进位(为0 或者 1)。
然后从后往前进行累加,并且通过配合当前进位来判断当前位置的值为0 还是 1

var addBinary = function(a, b) {
  let [num1,num2] = a.length > b.length ? [a, b] : [b, a]
  let result = '';
  let carry = 0
  for(let i = 0; i < num1.length;i++){
      let empty1 = num1[num1.length - i - 1];
      let empty2 = num2[num2.length - i - 1] || 0;
      if(+empty1 + +empty2 === 2){
          result = carry + result;
          carry = 1;
      }else if(+empty1 + +empty2 === 1){
          if(carry === 1){
              result = '0' + result;
          }else{
              result = '1' + result;
          }
      }else if(+empty1 + +empty2 === 0){
          result = carry + result;
          carry = 0;
      }
  }

    return carry? carry + result : result

};

当然这是我写的一个JS的版本,如果有更好的写法可以自己进行尝试。

方法2:

这道题可以通过将字符串转成十进制数。然后通过位运算实现加法的方式来解决。这里先不讲解该方法,在后面的位运算实现加减乘除里会对该方法进行实现。

4.2 力扣89 格雷编码

n 位格雷码序列 是一个由 2n 个整数组成的序列,其中:

  • 每个整数都在范围 [0, 2n - 1] 内(含 0 和 2n - 1)
  • 第一个整数是 0
  • 一个整数在序列中出现 不超过一次
  • 每对 相邻整数的二进制表示 恰好一位不同
  • 且 第一个 和 最后一个 整数的二进制表示 恰好一位不同

给你一个整数 n ,返回任一有效的 n 位格雷码序列 。

输入:n = 2 输出:[0,1,3,2] 解释: [0,1,3,2] 的二进制表示是 [00,01,11,10] 。

  • 00 和 01 有一位不同
  • 01 和 11 有一位不同
  • 11 和 10 有一位不同
  • 10 和 00 有一位不同 [0,2,3,1] 也是一个有效的格雷码序列,其二进制表示是 [00,10,11,01] 。
  • 00 和 10 有一位不同
  • 10 和 11 有一位不同
  • 11 和 01 有一位不同
  • 01 和 00 有一位不同

输入:n = 1 输出:[0,1]

方法1:

本题如果乍一看,似乎并不能通过逻辑的判断来进行解决。很容易就找不到思路,所以碰到这种情况,我们可以自己尝试着去列举,然后去发现其中的规律:

当n的值为1到3时,格雷编码的值应该为

  • n = 1 : 0 1

  • n = 2 : 00 01 11 10

  • n = 3 : 000 001 011 010 110 111 101 100

从n = 1 到 n = 2 的转换过程,我们可以发现其中的规律,反转后首位+1

一篇搞定JS的位运算(公式+力扣真题)--- 持续更新_第1张图片

从n = 2 到 n = 3 的转换过程也遵循着这个规律

一篇搞定JS的位运算(公式+力扣真题)--- 持续更新_第2张图片
有了上面的规律,我们就可以编写代码了:

var grayCode = function(n) {
    let ret = [0,1];
    for(let i=0;i<n - 1;i++){
        let empty = []
        let reverseRet = [...ret].reverse();
        for(let j = 0 ; j< reverseRet.length;j++){
            empty.push(reverseRet[j] + (1 << (i+ 1)))
        }
        ret.push(...empty)
    }
  return ret;
};

方法二

如果有数字电路的基础,我们可以通过公式法来解决格雷编码的问题

公式:( i >> 1 ) ^ i
格雷编码的每一位为当前索引右移一位,异或本身

一篇搞定JS的位运算(公式+力扣真题)--- 持续更新_第3张图片
由上面的公式,我们很容易写出代码

var grayCode = function(n) {
  let ret = []
  for(let i = 0; i< 1 << n ; i ++){
      ret.push((i >> 1) ^ i)
  }
  return ret;
};

4.3 力扣136 只出现一次的数字

给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。

输入:nums = [2,2,1]
输出:1

输入:nums = [4,1,2,1,2]
输出:4

如果不考虑位运算,我们很容易想到通过双层for循环来进行判断每个数字是否只出现了一次。同时也可以考虑使用Map的方式来降低时间复杂度,不过会增加额外空间。

但是如果使用位运算,我们就可以在常量空间以线性复杂度的方法解决该问题。
这道题的重点在于

相同的数字异或为0:因为相同的数字每一位都是相同的,所以异或值都为0,结果自然为0。
0异或任何数等于数字本身:因为0异或1为1,0异或0为0,结果都为自身

所以我们可以通过对数组进行异或求和,最后的结果就是只出现了一次的数字(相同的数字都异或成了0)

var singleNumber = function(nums) {
  return nums.reduce((value1,value2) => value1 ^ value2);
};

4.4 力扣137 只出现一次的数字Ⅱ

给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法且不使用额外空间来解决此问题。

输入:nums = [2,2,3,2]
输出:3

输入:nums = [0,1,0,1,0,1,99]
输出:99

这道题同样可以使用遍历+Map的数据结构进行解答,但是会创造额外的空间。

和上一道题4.3不同,因为如果对该数组进行异或求和,出现三次的数字异或为自己本身(0 ^ 本身)。所以并不能通过异或的方式对该题进行求解。

这道题,我们要借助上面的公式
求 x 的第k位数字: x >> k & 1
将 x 第k位数字置为1:x | (1 << k )

现在我们来说一下解题思路,我们知道在JS中是32位二进制数。也就是说每个数字的二进制位都在32位以内。

对于所有的数字,不过是在这32位上有的为0,有的为1。
我们可以把0抽象成一个有32个位置的数组,而nums里的每个数的二级制都会往这个数组的不同位置放一个1,而出现三次的数字就会在这个数组上不同位置上放了三个1。

一篇搞定JS的位运算(公式+力扣真题)--- 持续更新_第4张图片

所以我们可以这样理解,对于这个32位的数组,每个位置的数字 k,如果k % 3 = 0(为3的倍数),就说明这个位置一定是0或者它是来自于那个出现三次的数字。反之,如果k % 3 != 0,就说明这个位置一定来自于出现一次的数字。

我们可以把k % 3 = 0的位置全部变成0,而k % 3 != 0的位置变成1,那么这个结果就是出现一次的数字。

所以根据这个思路,以及上面的两个公式,我们可以写出代码

var singleNumber = function(nums) {
  let result = 0;
  for(let i=0;i<32;i++){
      let sum = 0;
      for(let j=0;j<nums.length;j++){
          let empty = ( nums[j] >> i ) & 1;
          sum += empty;
      }
      if(sum % 3 !== 0){
          result |= (1 << i)
      }
  }
  return result
};

4.5 力扣260 只出现一次的数字Ⅲ

给你一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。

你必须设计并实现线性时间复杂度的算法且仅使用常量额外空间来解决此问题。

输入:nums = [1,2,1,3,2,5]
输出:[3,5]
解释:[5, 3] 也是有效的答案

输入:nums = [-1,0]
输出:[-1,0]

还是类似的题目,依旧可以通过遍历+Map的数据结构来进行解决。但是有了之前的经验,这道题想考我们的一定不是这个。

是否可以通过4.4题,通过判断每一位k是否为2的倍数的方式来解决问题呢?似乎不能,因为这两个不同的数字有可能在某一位都为1,从而打乱这个规律。
那是否可以通过4.1题,异或的方式来对该问题进行解决呢?可是如果对nums进行异或求和,得到的结果应该为 结果1 ^ 结果2

所以,如果我们有一种方法能够将结果1 ^ 结果2分开,从而得到正确的结果,这道题就迎刃而解了。

现在我们说解题思路,对于nums的异或求和,我们用K来代替,两个结果用num1和num2来代替。对于K来说,它为num1 ^ num2。
在K的二进制最后一位1,在相同的位置上,num1和num2一定是一个为1,一个为0

而对于nums数组中的其他数字,在该位置上要么是0要么是1,而且都出现了两次,所以我们可以根据这个位置为0或者1,将数组分为两部分。第一部分为num1以及其他在该位置和num1相同的数字,并且它们都出现了两次,第二部分为num2以及其他在该位置和num2相同的数字,并且它们也都出现了两次。
我们对这两部分分别异或,得到的就是num1和num2。

所以这道题我们依赖的公式为
返回 x 的最后一位1:x & -x

根据上面的过程和公式我们可以得到代码:

var singleNumber = function(nums) {
  let res = 0;
  nums.forEach(element => {
    res ^= element;
  });

  let empty = res & -res;

  let left = 0,right = 0;
  nums.forEach(element => {
    if(element & empty){
      left ^= element
    }else{
      right ^= element
    }
  })
  return [left,right]
};

4.6 力扣190 颠倒二进制位

颠倒给定的 32 位无符号整数的二进制位。

输入:n = 00000010100101000001111010011100
输出:964176192 (00111001011110000010100101000000)
解释:输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596,
因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。

输入:n = 11111111111111111111111111111101
输出:3221225471 (10111111111111111111111111111111)
解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,
因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111

如果前面的习题都已经掌握,对于这一道题来说,就已经不算困难了。
我们只需要每次拿到 n 的最后一位 k(公式在上面),然后依次排开就是我们最后的结果。
怎么按顺序排开每次拿到的最后一位数字 k 呢?只需要将 k 对应左移(例如第一次拿到的k就要左移32位),然后和0进行或运算。最终就可以得到我们想要的结果。

这里面需要注意的是,在JS中,是具有符号位的,所以在进行右移的时候,要使用>>>进行有符号位移。而最后的结果需要通过 >>> 0 转换位无符号整数。而在JS中只能通过>>>0转换成无符号整数。

var reverseBits = function(n) {
    let result = 0;
    for(let i=0;i<32 && n > 0;i++){
        result |= (n & 1) << (31 - i);
        n = n >>> 1
    }
    return result >>> 0
};

4.7 力扣191 位1的个数

编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。

输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 ‘1’。

输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 ‘1’。

哇,这道题如果对上面的公式已经掌握,直接就是手到擒来。我们可以每次都将 n 的最后一位1变成0(公式法),记录1的出现次数,直到 n 的值变成了0,也就是最后我们想要的结果了。

var hammingWeight = function(n) {
    let res = 0;
    while(n != 0){
        n = n & (n - 1);
        res ++
    }
    return res;
};

4.8 力扣201 数字范围按位与

给你两个整数 left 和 right ,表示区间 [left, right] ,返回此区间内所有数字 按位与 的结果(包含 left 、right 端点)。

输入:left = 5, right = 7
输出:4

输入:left = 0, right = 0
输出:0

输入:left = 1, right = 2147483647
输出:0

这道题光看题目来讲,似乎直接从left直接与到right就能得到答案,但是作为力扣的一道中等题。一定不是想这么考察我们的。而且即便这么写,在力扣中例三也无法通过编译。

所以我们要找到能降低时间复杂度的方法,我们先把例子列出来看一下是否能通过过程,来找到一些规律。

一篇搞定JS的位运算(公式+力扣真题)--- 持续更新_第5张图片
从上面的图我们可以发现,似乎 left 一直到 right 与操作的结果,是left和right公共的前部分,然后后面全部补0.

大家也可以多举一些例子,验证一下这个方案。其实原理就是,从right向左进行与操作,其实就是一直在将末尾的1变成0。所得到的值只要比left大,那么一定是可以继续操作的。但如果一旦比left小,那么这个值一定是最后的结果。

var rangeBitwiseAnd = function(left, right) {
    while(left < right){
        right = right & (right - 1)
    }
    return right
};

4.9 力扣231 2的幂

给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。

如果存在一个整数 x 使得 n == 2x ,则认为 n 是 2 的幂次方。

输入:n = 1
输出:true
解释:20 = 1

输入:n = 16
输出:true
解释:24 = 16

首先我们要知道,满足2的幂的数字,它二进制位是只有一位1的。
在公式里面 x & (x - 1) 是将二进制位的最后一位1置为0。如果x的二进制位只有一个1,那么x & (x - 1) 之后的数字,一定是0。我们就可以通过这个方法来进行判断,数字是否为2的幂。

var isPowerOfTwo = function(n) {
    return n > 0 && n !== 0 && (n & (n - 1)) === 0;
};

4.10 力扣268 丢失的数字

给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。

输入:nums = [3,0,1]
输出:2
解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。

输入:nums = [0,1]
输出:2
解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中。

对于这道题,又要用到两个相同的数异或结果位0这个特性了。说到这里如果有思路的就不要看了,直接动手写。
对于这道题我们可以分成两部分来看,第一部分就是nums数组A,第二部分就是【0,n】这个数组B。

对于A,B两个数组,二者之间只差了两个数字,A多了一个最大数,B少了一个缺失的数字,对于这个最大数,我们可以看的出来就是数组长度。

所以如果让A ^ B ^ nums.length,得到的结果不就是缺失的数字了嘛。
在这里我们用JS一行代码来搞定:

var missingNumber = function(nums) {
  return nums.reduce((val1,val2,index)  => val1 ^ val2 ^ index,nums.length)
};

4.11 力扣318 最大单词长度乘积

给你一个字符串数组 words ,找出并返回 length(words[i]) * length(words[j]) 的最大值,并且这两个单词不含有公共字母。如果不存在这样的两个单词,返回 0 。

输入:words = [“abcw”,“baz”,“foo”,“bar”,“xtfn”,“abcdef”]
输出:16
解释:这两个单词为 “abcw”, “xtfn”。

输入:words = [“a”,“ab”,“abc”,“d”,“cd”,“bcd”,“abcd”]
输出:4
解释:这两个单词为 “ab”, “cd”。

这道题的考点,主要在于如何判断两个单词是否具有公共字母,比较常规的做法是,通过双层for循环进行遍历,判断是否有相同的字母。

但是如果通过位运算,这个点就会简单许多。

假如我们先设定一个0作为32位的二进制目标值(32个0),对于两个单词A和B,A单词的每个字母,我们都看成一个数字(转成ASCII码 - 97),这个数字一定是小于32的,然后在二进制目标值对应位置变成1,得到结果@A

判断B是否和A具有相同的字母的时候,就简单多了,我们可以对B进行相同操作得到结果@B。对@A和@B进行与运算,如果结果不为0,那么就说明A和B具有相同字符
同样我们也可以,拿到B每个字符对应的数字,去看@A对应位置上的值是否为1,如果为1就说明是具有相同字符的。

OK,有了上面的方法,做出这道题就不难了,这里写一个我写过的JS的版本:

var maxProduct = function(arr) {
    const ifSame = (str1,str2) =>{
      let empty = 0;
      for(let i=0;i<str1.length;i++){
        const num = 1 <<  str1[i].charCodeAt() - 97;
        empty |= num;
      }
      for(let i=0;i<str2.length;i++){
        const num = str2[i].charCodeAt() - 97;
        let empty2 = (empty >> num) & 1;
        if(empty2 === 1){
          return true
        }
      }
      return false;
    }

    let max = 0;
    for(let i=0;i<arr.length - 1;i++){
      for(let j=1;j<arr.length;j++){
        if(!ifSame(arr[i],arr[j]) && arr[i].length * arr[j].length > max){
          max = arr[i].length * arr[j].length
        }
      }
    }
    return max
};

4.12 力扣342 4的幂

给定一个整数,写一个函数来判断它是否是 4 的幂次方。如果是,返回 true ;否则,返回 false 。

整数 n 是 4 的幂次方需满足:存在整数 x 使得 n == 4x

输入:n = 16
输出:true

上面有一道题叫做2的幂,那么4的幂一定满足是2的幂,所以在n的二进制里应该只有一个1。
但是不同于2的幂,只满足于这一个点是不行的。

所以我们可以看一下:
1 ------> 1
100 ------> 4
10000 ------> 16
1000000 ------> 64

对于4的幂,二进制中1的位置一定出现在奇数位置。对于这个特点,我们可以通过代码来实现,只需要每次让 n = n >> 1,直到n的值为0,然后记录运算的次数即可。

var isPowerOfFour = function(n) {
    if((n & (n - 1)) !== 0){
      return false
    }
    let bit = 0;
    while(n > 0){
      n = n >> 1;
      bit++
    }
    return ((bit - 1) & 1) === 0
};

4.13 力扣389 找不同

给定两个字符串 s 和 t ,它们只包含小写字母。

字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。

请找出在 t 中被添加的字母。

输入:s = “abcd”, t = “abcde”
输出:“e”
解释:‘e’ 是那个被添加的字母。

输入:s = “”, t = “y”
输出:“y”

如果对上面的题都有自己做一遍,那么如果碰到字符串类似的题(字符串中只包含小写字母),那么我们都可以用位运算来尝试一下。为什么呢?因为小写字母只有26位,而JS中二进制有32位。每一个小写字母都可以对应一个位置。

而这道题好像不需要那么复杂,非常简单,我们知道两个相同的数异或为0,而字母可以转换为ASCII码。所以这道题通过异或和,就是那个添加的字母。已经非常简单了。

var findTheDifference = function(s, t) {
    let left,right;
    for(let i=0;i<s.length;i++){
        left ^= (s[i].charCodeAt())
    }
    for(let i=0;i<t.length;i++){
        right ^= (t[i].charCodeAt())
    }
    return String.fromCharCode(left ^ right)
};

4.14 力扣393 UTF-8 编码验证

给定一个表示数据的整数数组 data ,返回它是否为有效的 UTF-8 编码。

UTF-8 中的一个字符可能的长度为 1 到 4 字节,遵循以下的规则:

对于 1 字节 的字符,字节的第一位设为 0 ,后面 7 位为这个符号的 unicode 码。
对于 n 字节 的字符 (n > 1),第一个字节的前 n 位都设为1,第 n+1 位设为 0 ,后面字节的前两位一律设为 10 。剩下的没有提及的二进制位,全部为这个符号的 unicode 码。
这是 UTF-8 编码的工作方式:

Number of Bytes | UTF-8 octet sequence
| (binary)
--------------------±--------------------------------------------
1 | 0xxxxxxx
2 | 110xxxxx 10xxxxxx
3 | 1110xxxx 10xxxxxx 10xxxxxx
4 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

输入:data = [197,130,1]
输出:true
解释:数据表示字节序列:11000101 10000010 00000001。
这是有效的 utf-8 编码,为一个 2 字节字符,跟着一个 1 字节字符。

输入:data = [235,140,4]
输出:false
解释:数据表示 8 位的序列: 11101011 10001100 00000100.
前 3 位都是 1 ,第 4 位为 0 表示它是一个 3 字节字符。
下一个字节是开头为 10 的延续字节,这是正确的。
但第二个延续字节不以 10 开头,所以是不符合规则的。

这道题在leetcode是一道通过率不到50的中等题,但其实只是题目太长比较劝退。其实只要知道给你一个数组,是不是由UTF-8的字符组成的。
而UTF-8的字符只有4种,就是上面的四个例子。

所以对于data数组,我们其实只需要从头开始,每次拿一个,看它是几字节的。然后再看后面的是否满足,如果满足就继续,不满足就返回false。直到data数组为空。

例如第一个出来一个四字节的字符,那么后面三个字符都要10开头。

var validUtf8 = function(data) {
    while(data.length){
      let start = data.shift();
      let start2 = start >> 3;
      start = start2 >> 1;
      if(start2 === 30){
        let first = data.shift();
        let second = data.shift();
        let third = data.shift();
        if(first >> 6 !== 2 || second >> 6 != 2 || third >> 6 != 2){
          return false;
        }
      }else if(start === 14){
        let first = data.shift();
        let second = data.shift();
        if(first >> 7 !== 1 || second >> 7 != 1){
          return false;
        }
      }else if(start === 12 || start === 13){
        let first = data.shift();
        if(first >> 7 !== 1){
          return false;
        }
      }else if(start <= 7 && start >= 0){
        
      }else{
        return false
      }
    }
    return true
};

这是我自己刷题的时候写的一个版本,可能不太好看,但是理解好了自己能写出更好的,才是最重要的。

4.15 力扣401 二进制手表

二进制手表顶部有 4 个 LED 代表 小时(0-11),底部的 6 个 LED 代表 分钟(0-59)。每个 LED 代表一个 0 或 1,最低位在右侧。

例如,下面的二进制手表读取 “3:25” 。

输入:turnedOn = 1
输出:[“0:01”,“0:02”,“0:04”,“0:08”,“0:16”,“0:32”,“1:00”,“2:00”,“4:00”,“8:00”]

输入:turnedOn = 9
输出:[]

对于这道题,由于又是这种位置有固定个数的,我们就可以通过位运算来解决,对于小时的四位和分钟的六位,我们可以通过一个十位的二进制数来代表,而对于二进制种1的个数对我们来讲已经很简单了。所以也很容易判断亮起来的灯有几个。

所以我们可以枚举所有的时间,看看它的二进制中1的个数是否等于turnedOn。这里面用了leetcode的官方题解。

var readBinaryWatch = function(turnedOn) {
    const ans = [];
    for (let h = 0; h < 12; ++h) {
        for (let m = 0; m < 60; ++m) {
            if (h.toString(2).split('0').join('').length + m.toString(2).split('0').join('').length === turnedOn) {
                ans.push(h + ":" + (m < 10 ? "0" : "") + m);
            }
        }
    }
    return ans;
};

但是对于一道题,如果是通过枚举方式来解决的话,就很让人觉得在取巧。而这道题同时也有回溯的方法来解决。
因为即使是手动枚举,也可以通过回溯的方式通过代码体现出来,这里我写一个我自己写的回溯版本。

var readBinaryWatch = function(turnedOn) {
    const ref = (num,target,res,arr,max) => {
      if(arr.length === num){
        if(arr.length === 0){
          res.push(0)
        }else if(arr.length === 1){
          res.push(+arr[0])
        }else{
          let num = arr.reduce((item1,item2) => +item1 + +item2)
          if(res.indexOf(num) === -1){
            res.push(num)
          }
        }
      }
      for(let i=0;i<target.length;i++){
        let sum = arr.length ?  arr.reduce((item1,item2) => +item1 + +item2) : 0
        if(sum + target[i] <= max && arr.indexOf(target[i]) === -1){
         ref(num,target,res,[...arr,target[i]],max)
        }
      }
    }
    
    const result = []
    for(let top=0;top<=turnedOn;top++){
      let bottom = turnedOn - top;
      let leftRes = [];
      let rightRes = []
      ref(top,[8,4,2,1],leftRes,[],11);
      ref(bottom,[32,16,8,4,2,1],rightRes,[],59);
      for(let i=0;i<leftRes.length;i++){
        for(let j=0;j<rightRes.length;j++){
          result.push('' + leftRes[i] + ':' + (rightRes[j] < 10 ? '0' + rightRes[j] : rightRes[j]))
        }
      }
    }
    return result;
};

4.16 力扣461 汉明距离

两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。

给你两个整数 x 和 y,计算并返回它们之间的汉明距离。

输入:x = 1, y = 4
输出:2
解释:
1 (0 0 0 1)
4 (0 1 0 0)
↑ ↑
上面的箭头指出了对应二进制位不同的位置。

输入:x = 3, y = 1
输出:1

最后来一个简单的位运算题,这道题一看就知道通过x ^ y得到res,而res中1的个数就是最终的答案。

var hammingDistance = function(x, y) {
    let res = x ^ y;
    let index = 0;
    while(res > 0){
        index++;
        res = res &( res - 1)
    }
    return index;
};

你可能感兴趣的:(JS基础,javascript,leetcode,开发语言)