【LeetCode刷题笔记】位运算

231. 2 的幂

解题思路:
  • 1. 除法 不断循环判断, 如果能被 2 整除,就不断除以 2 ,直到不能被 2 整除为止,最后结果如果是 1 ,说明可以除尽,是 2 的幂次方,否则就不是。
  • 特判: 负数 0 不可能由 2 x 次方得到 ,直接返回 false

如果一个数是 2 的幂,不断除以 2 最终会变成 1:16/2=8, 8/2=4, 4/2=2, 2/2=1 

解题思路:
  • 2. 判断数字的二进制中是否只含1个1 ,因为如果一个数是  的幂,那么该数的二进制中只有 1个1
  • 判断二进制中是否只含 1个1 有两种方法:
  • 1)利用 n & (n - 1) 去掉 n 的最后一个  1 ,判断结果是否是  即可 ,如果是  说明只含 1个1
  • 2)利用 n & -n 获取到 只含最后一位1 的数,判断结果 跟原数字是否相等 即可 ,如果跟原数字相等,说明 只含 1个1

可以发现整型范围内如果是 2 的幂次,那么二进制中一定只有一个 1,所以只需判断二进制中是否只包含一个 1 即可,有两种方法:

  • n & (n - 1) == 0 :去掉最后一个 1,看是否为 0
  • n & -n == n :只保留最后一位 1,看是否跟原数相同

n & (n - 1) n & -n 是两个常用操作,对于这两个操作的解释如下:  

下面举例说明一下,首先看 n & (n - 1) 的操作: 

  • 如果是 2 的幂,例如 2^4 :

  • 可以发现如果一个数是 2 的幂,执行完 n & (n - 1) 的结果会是 0
  •  如果不是 2 的幂,例如 14:

  • 可以发现如果一个数不是 2 的幂,执行完 n & (n - 1) 的结果不是 0 ,只是将最后一个二进制位上的 1 变为了 

接着看 n & -n 的操作:   

  • 如果是 2 的幂,例如 2^4 : 

  • 可以发现如果一个数是 2 的幂,执行完 n & -n 的结果和原来的数自身相等
  • 如果不是 2 的幂,例如 248:

  • 可以发现如果一个数不是 2 的幂,执行完 n & -n 的结果不会和原来的数字相等,只会保留所有二进制位的 1 中最后一个 1 ,而其他的  都置为了 

解题思路:
  • 3. 判断是否为最大 2 的幂的约数 ,在整型范围内,最大的  2 的幂为 2^30 记为 BIG , 我们只需判断 n 是否是 BIG 的约数即可。(即判断 BIG 是否是 n 的整数倍 BIG % n 是否等于 0 )
  • 注意点: 2^30 可以直接写成 1 << 30 。因为最大的整数是 2^31 - 1 ,所以可知道最大的 2 的幂是 2^30

 

326. 3 的幂

解题思路:
  • 1. 除法 ,不断循环判断,如果是 3 的倍数,就 除以 3 ,直到不能被 3 整除为止,最后结果如果是 1 ,说明可以除尽,是 3 的幂次方 ,否则就不是。
  • 特判: 负数 0 不可能由 3 x 次方得到,直接返回 false

如果一个数是 3 的幂,不断除以 3 最终会变成 1:81/3=27, 27/3=9, 9/3=3, 3/3=1 

 

解题思路:
  • 2. 判断是否为最大 3 的幂的约数 ,在整型范围内,最大的 3 的幂为 3^19 = 1162261467 ,记为 BIG , 我们只需判断 n 是否是 BIG 的约数即可。(即判断 BIG 是否是 n 的整数倍 BIG % n 是否等于 0 )
  • 注意点:求 3^19 ,可以 利用50. Pow(x, n)代码快速幂求解,也可以直接 Math.pow(3, 19)

 

这个题相比于前面一道而言,无法使用位运算技巧来解决,对于方法 2 的解法,困难点在于不借助计算器的情况下,我们很难知道整型范围内最大的 3 的幂次的值是多少,如果通过代码计算则肯定会用到循环或者递归,而题目又不让你用循环或者递归。。 所以这道题又是脑筋急转弯的题,意义不大。

191. 位1的个数

解题思路:
  • 1. 利用 n & (n - 1) 每次去掉最后一位的 1 , 只要 n != 0 就一直执行这个操作,每执行  次就 计数+1 。最终 n 会变成 0 退出循环。
  • 2. 判断  n &  的值,如果结果是  1 计数 累加 ,然后 n 每次 右移 1 位, 其实是在不断检查n的最低位是否为1。循环条件: n 总共右移 32 次结束,也可以使用 n != 0
  • 3. 将 1 每次左移 i 位 ( i [0,31] ),然后和 n 相与,看结果是否是 0 不是  0 计数

方法 1 的代码: 

 

该方法的时间复杂度:O(logn)  

方法 2 的代码:  

注意,这里如果使用 n != 0 作为循环条件,n 要使用无符号右移,否则会出现循环。 

方法 3 的代码:  

 

338. 比特位计数

解题思路:
  • 1. 位运算 ,遍历 [0, n] 中的每个 i , 调用191.题代码统计1的个数即可。

 

 这个时间复杂度是 O(nlogn)

解题思路:
  • 2. 动态规划 + 最高有效位 ,创建长度 n+1 dp 数组,令 dp[i] 表示数字 i 的「 位1比特数 ,初始 dp[0] = 0 ,  遍历[1, n],求解剩余每个 dp 值,最终返回 dp 数组。
  • 对于遍历遇到的每个正整数 x 如果可以知道 ≤ x 的最大的正整数 y ,且  y 2 的整数次幂,则  y 的二进制只有最高位是 1 ,其余都是 0 ,此时称 y x 的「 最高有效位 」。
  • z = x − y ,显然 0 ≤ z < x x z 二进制只多 1 1 ,则 dp[x] = dp[z] + 1 ,因此只需找到这样的一个 y 它是 2 的整数次幂即可,
  • 如何判断一个正数二进制上是否只有最高位是 1 :可以利用公式 n & (n - 1) 去掉最后一位的 1 ,然后看结果是否为 0 ,如果为 0 ,则该数就是要找的 y 。(也就是2的幂)
  • 由于是从小到大遍历,因此遍历到 x dp[x − y]  的值已知,则 dp[x] = dp[x − y] + 1

注意:这里的y要定义在for循环的外面,当x2的幂时,y其实是上次计算的值,它距离本次的x是离它最近的2的幂。x2的幂时,y等于xdp[x] = dp[0] + 1 = 12的幂也确实只有 1 个 1  

这个过程其实就是每次利用了距离 x 最近的 2 的幂来计算当前的 dp 值 ,下表展示了 [1 - 10] 中的每个 x 的 dp 值计算过程,可以参考理解:

从表中可以看出 dp 计算时等号右边的 dp 值一定是在前面计算过了的,另外,当前计算出的 y 的值,可能对下一个枚举的 x 值有效,因此这就是为什么 y 要定义在 for 循环的外面,我们要记住它。

解题思路:
  • 3. 动态规划 +  最低有效位 创建长度 n+1 dp 数组,令 dp[i] 表示数字 i 的「 位1比特数 初始 dp[0] = 0 ,  遍历[ 1, n] ,求解剩余每个 dp 值,最终返回 dp 数组。
  • 对于 遍历遇到的每个正整数 x ,   x/2 相当于 x 右移 1 位, 对于 偶数 ,二进制最低位是 0 ,因此右移一位后 1 的个数不变, 对于 奇数 ,二进制最低位是 1 ,因此右移一位后会少 1 1
  • 因此如果 dp[x/2] 的值已知,则可以得到 dp[x] 的值: 如果 x 偶数 ,则 dp[x] = dp[x/2] ,如果 x 奇数 ,则 dp[x] = dp[x/2] + 1
  • 上述两种情况可以合并写成: dp[x] = dp[x/2] + (x & 1) 因为是从小到大遍历,所以遍历到 x dp[x/2]  的值肯定已知。

 

下表展示了 [1

  • 28
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值