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

2023-05-16

摘要

位操作(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

在这里插入图片描述

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

在这里插入图片描述
有了上面的规律,我们就可以编写代码了:

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
格雷编码的每一位为当前索引右移一位,异或本身

在这里插入图片描述
由上面的公式,我们很容易写出代码

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。

在这里插入图片描述

所以我们可以这样理解,对于这个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就能得到答案,但是作为力扣的一道中等题。一定不是想这么考察我们的。而且即便这么写,在力扣中例三也无法通过编译。

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

在这里插入图片描述
从上面的图我们可以发现,似乎 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;
};
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • Ubuntu18.04切换Python版本

    转载自 xff1a Ubuntu18 04 切换 Python 版本 前言 Ubuntu18 04 默认安装了两个版本 Python2 7 和 Python3 6 查看可用二进制文件 ls usr bin python 过程 使用 upda
  • 解决ubuntu1604联网以后网页还是打不开的问题

    ubuntu系统连接正常的联网的网线但是网页还是打不开 xff0c 所有联网的软件也打不开 xff0c 在路由器工作正常的情况下 xff0c 可能出现的问题为dns解析异常 xff0c 关于dns解析异常的解决方法 xff1a 这段时间在u
  • 操作系统--线程并发实验三

    操作系统 线程并发实验三 一 实验目的 线程的运行时并发的 xff0c 如果互不相干的线程交替运行不会产生问题 但是如果有共享资源 合作关系的线程之间由于交替运行可能产生问题 xff0c 例如偶尔出现程序的结果不正常 理解临界区的概念 xf
  • 安装OOQP遇到问题

    Ubuntu20 04 安装OOQP遇到问题 OOQP安装 OOQP安装 MA27是OOQP的依赖 在安装MA27时容易出现找不到fortran77等情况 xff0c 在配置这些环境时容易出现其他错误导致系统环境出现问题 选择其他版本的安装
  • 15个好用的百度网盘搜索引擎

    15个好用的百度网盘搜索引擎 前言 分享 15 个好用的百度网盘搜索引擎 xff0c 方便大家搜索百度云网盘分享的资源文件 挑出来这 15 个效果还不错 xff0c 都可以正常使用 挑选标准 xff1a 免费 xff0c 大部分不登录可用
  • 操作系统死锁实验六

    操作系统死锁实验六 一 实验目的 如果一个进程集合中的每个进程都在等待只能由该进程集合中的其他进程才能引发的事件那么该进程集合就是死锁的 产生死锁的必要条件 xff1a 互斥 xff1b 请求资源和保持已获得资源不释放 xff1b 不可抢占
  • 修复 Windows11 打不开 Windows安全中心

    修复 Windows 打不开 Windows安全中心 遇到以上问题我们直接上解决方法 win10的话直接WIN 徽标 43 X键 win11 菜单栏输入 PowerShell 管理员启动 管理员权限打开PowerShell xff0c 依次
  • webstorm/idea 配置less环境

    看了一下发现大多数教程少了最关键的一步 如果这个lessc不能自动识别的话 需要手动寻找lessc cmd的路径 xff0c 可以在终端中通过 where lessc查找 复制lessc cmd位置就可以了
  • 自定义http钩子

    简单创建一个自定义http钩子函数 span class token keyword import span span class token punctuation span useState span class token punct
  • React Redux 工具包 Redux Toolkit 初步学习

    Redux 工具包 xff08 Redux Toolkit xff09 的目标是帮助简化常见的 Redux 用例 它并不是你想要用 Redux 做的所有事情的完整解决方案 xff0c 但它应该使你需要编写的许多与 Redux 相关的代码变得
  • 卫星导航模拟器GSS7000测试NTRIP RTK--以Ublox F9P 为例.rtklib原始观测量解算固定解FIX

    GSS7000 Ntrip 测试指南 Ntrip Networked Transport of RTCM via Internet Protocol 通过互联网进行RTCM网络传输的协议 是在互联网上进行RTK数据传输的协议 Ntrip是一
  • Ubuntu网络调试助手安装后无法打开

    转载自 解决Ubuntu网络调试助手安装后无法打开问题
  • 微机原理与接口技术之8060微处理器

    微机原理与接口技术之Intel8060微处理器 这篇bolg主要讲的是8060微处理器的内部结构 xff0c 引脚功能以及总线时序 8086内部结构 xff1a 8086CPU是由执行指令部件EU和总线接口部件BIU两部分注组成 1 EU部
  • Qt的三个基类QObject、QApplication和QWidget

    一 Qt介绍 1 概述 Qt是一个跨平台的C 43 43 图形用户界面应用程序框架 由挪威TrollTech公司出品 1996年Qt进入商业领域 xff0c 它已经成为全世界范围内数千种成功的应用程序的基础 Qt也是流行的Linux桌面环境
  • 锂电池串联放电并联充电自动转换电路

    直接通过5v充电器给串联锂电池组充电可以大大提高充电器的利用率 毕竟现在手机充电器都有 再去买个专用的锂电池平衡充电器又感觉没啥必要 一般给串联锂电池组充电的方案就是通过升压模块将5v升压后再充电 感觉有弊端 1 一般没有平衡充电功能 造成
  • Linux(Ubuntu)配置Cuda,Pytorch,Anaconda

    近期需要在Linux xff08 Ubuntu20 04 xff09 上运行一个工程 xff0c 需要搭建相关环境 xff0c 这是首次在Linux系统上完成anaconda xff0c cuda xff0c 及Pytorch的下载与配置
  • Visual Studio配置OpenGL

    近期工作中需要用到OpenGL 而之前一直是用Opencv工作 xff0c 这就需要在VS上配置OpenGL 因为是首次在VS上配置OpenGL xff0c 以备自己和有需要的小伙伴不时之需 我的VS是2022版的 xff0c 但配置流程各
  • Pycharm终端问题: python : 无法将“python”项识别为 cmdlet、函数、脚本文件或可运行

    发现这个问题的起因是我打算尝试用Django练习做网站 xff0c 需要在Pycharm终端输入一些命令以运行脚本 xff0c 我的Pycahrm配置了anaconda xff0c 但在终端运行命令时一直报错 xff1a python 无法
  • Python:把列表内容按行数写入txt

    事情的起因是我需要把一个元素全为数字的列表按固定列数写入txt文件 xff0c 也就是每行几个元素 xff0c 用逗号隔开 看了一些网上的分享觉得都不太合适 xff0c 于是自己想了一个办法 xff0c 一行代码解决 xff0c 废话少说
  • 解决Git提交代码报错: ERROR: commit xxxxx: missing Change-Id in message footer

    在近期的工作中完成代码修改提交代码时Git报错并提示提交不成功 xff0c 具体错误如下 xff1a 原因是Change Id缺失 至于解决方法 xff0c Git在报错时已经提示了 xff0c 如下图黄框所示 xff1a 首先 xff0c

随机推荐

  • 如何实现用串口助手实时绘制16位数据波形图

    先和大家kuan两句 xff0c 哈哈 因为之前参加智能车想用波形显示来调节PID xff0c 找了很多工具也没有成功 xff0c 心里也知道串口一次就是只能发送八位数据 xff0c 很多时候可以用字符显示16位的 xff0c 但是就不是数
  • STM32之中断方式实现串口通信

    中断方式实现串口通信 一 创建项目二 编写代码三 运行四 总结 一 创建项目 创建一个STM32f103c8的STM32CubeMX项目 xff1a SYS设置 xff1a RCC设置 xff1a 时钟树设置 xff0c 输入72后回车 x
  • 1.(1)数据结构之链表-typedef的用法

    本人坚持更新C语言 xff0c 数据结构 xff0c 操作系统知识 xff0c 可以收藏 xff0b 关注随时了解 x1f61c x1f61c x1f61c 目录 我们在之前学习结构体的时候 xff0c 是如何定义结构体的呢 xff1f t
  • gazebo 中创建含有二维码的墙的模型

    1 新建空白墙的模型 在gazebo中添加一个Edit gt Building Editor xff0c 生成sdf文件 xff0c 放在 gazebo models文件夹下 如图Untitled1 编辑model sdf文件 xff0c
  • 在vscode中开发arduino编译巨慢解决办法

    每次在vscode中 编译Arduino花费的时间巨长 xff0c 等的好烦 xff0c 仔细一看每次在Arduino 输出控制台上会出现一个警告 Warning Output path is not specified Unable to
  • 工作空间中的devel和build文件夹可以删掉

    工作空间中的devel和build文件夹可以删掉 xff0c 再cmake就可以产生
  • IOT的核心—无线通讯模块

    文章目录 前言一 IOT是什么 xff1f 1 IOT的运用 1 智能家居 2 无线控制 2 IOT总结 三 如何从互联网转换为物联网简述芯百特的CB2401与CB2402 1 CB2401介绍内部结构与管脚图产品应用评估板原理图 2 CB
  • STM32F103C8T6 串口3(USART3) 只能发不能收

    问题原因 xff1a 今天因为上述问题 困扰一天 xff01 最后发现是 PB8 9 xff08 配置输出 xff09 硬件短路了 xff01 问题现象 xff1a STM32F103C8T6 串口3 USART3 只能发不能收 xff01
  • eNSP第四篇:IP地址,逻辑接口,接口类型,三层路由接口,二层路由接口

    IP地址 xff0c 逻辑接口 xff0c 接口类型 xff0c 三层路由接口 xff0c 二层路由接口 私有IP地址的范围 IP范围 默认掩码 A类 10 0 0 0 10 255 255 255 255 0 0 0 B类 172 16
  • IMU及磁力计AHRS系统控制(一):传感器物理实现原理

    AHRS系统前言 AHRS是 Attitude and heading reference system 的英文缩写 xff0c 百度对此的解释是 航姿参考系统 xff0c 按笔者比较浅薄的理解就是在计算平台上通过算法处理一套部署在被控对象
  • 归并排序(C语言)详解

    记录学习第五天 今天记录一下归并排序 xff0c 因为在csdn里面没有找到特别清楚的解析 xff0c 所以想自己写的认真一点 xff0c 也查阅了一些资料 xff0c 通过这篇博客记录一下 xff1b 归并排序 xff0c 光看字面 xf
  • HTML的块级元素(常用整理)

    emmm xff0c 最近想整理复习一下前端的基础 xff0c 最开始的HTML想了好久也没想好怎么写 xff0c 最后也是决定以行块这样整理 xff0c 再在后面补充吧 说到HTML xff0c 什么是HTML呢 xff1f 什么是 HT
  • JS实现快速排序(代码+讲解)

    OK xff0c 排序这一个篇章也快要结束了 这一篇主要说的是快速排序 xff0c 说的方式主要还是先说原理 xff0c 然后再用代码来进行实现 所谓快速排序 xff0c 就是分为三步走 xff1a 第一步 xff1a 选择第一个数字分离出
  • Object.defineProperty方法(详解)

    OK xff0c 这一篇主要想说一下Object defineProperty这个方法 这个方法也是Vue数据双向绑定原理的常见面试题 所以也是有必要好好掌握的哦 首先我们知道JS中是支持面向对象编程的 xff0c 也是有着对象和类这样的概
  • 原生JS实现Promise(详解)

    摘要 首先呢 xff0c Promise是异步中比较重要的知识点 xff0c 学习的最好方法就是掌握它的基本原理 所以这一篇主要说一下如何用JS来实现一个自己的promise 构造函数 首先我们来看一下我们是如何使用promise的 xff
  • 解决winscp连接ubuntu虚拟机连续超时

    1 禁用虚拟机网络 在windows系统找到网络适配器 xff0c 禁用VMnet1和VMnet8 2 更改网络连接模式并测试网络是否连通 菜单栏 虚拟机 设置 网络适配器 xff0c 将网络模式改为桥接模式 xff0c 勾选 复制物理网络
  • Http的各种请求方法(详解)

    摘要 我们知道 xff0c 当我们访问各种网页的时候 xff0c 之所以能够看到页面 xff0c 根本原因是发送了http请求然后得到了响应 xff0c 从而页面才会弹出来 再或者我们上传一些照片和视频时 xff0c 之所以可以上传成功也是
  • React中ref的使用方法和使用场景(详解)

    摘要 不管在Vue中还是React xff0c 如果我们想使用一个元素的DOM xff0c 不需要通过JS中操纵DOM的方法 xff0c 它们提供了一个专属的API就是ref 而Vue中的ref可能比较简单 xff0c 这一篇主要讲一下如何
  • 原生JS的拖拽属性draggable(详解)

    摘要 作为h5新增的属性draggable xff0c 它能够给与一切的html元素拖动的效果 而在这个属性之下 xff0c 也有着关于拖动效果的各个方法 而这一篇文章 xff0c 主要就是说一下关于draggable属性的使用以及工作场景
  • 一篇搞定JS的位运算(公式+力扣真题)--- 持续更新

    摘要 位操作 xff08 Bit Manipulation xff09 是程序设计中对位模式或二进制数的一元和二元操作 在许多古老的微处理器上 xff0c 位运算比加减运算略快 xff0c 通常位运算比乘除法运算要快很多 在现代编程语言中