「随手记」Java中关于位运算的一些小技巧--LeetCode1342

128 阅读5分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第5篇文章,点击查看活动详情

引言

今天在刷LeetCode1342题目的时候,发现它与2的n次方相关,然后还有一些判断奇数偶数的问题,就尝试向使用位运算来做,毕竟在计算机中的位运算是特别快的,但一般正常工作中用的很少。

题目是LeetCode1342:将数字变成 0 的操作次数 LeetCode1342

知识预备

位运算基础知识

符号描述运算规则
&两个位都为1时,结果才为1
/两个位都为0时,结果才为0
异或两个位相同为0,相异为1
~取反0变1,1变0
<<左移各二进位全部左移若干位,高位丢弃,低位补0
>>右移各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)

原题

给你一个非负整数 num ,请你返回将它变成 0 所需要的步数。 如果当前数字是偶数,你需要把它除以 2 ;否则,减去 1 。

示例 1:

输入: num = 14 输出: 6

解释:

  • 步骤 1) 14 是偶数,除以 2 得到 7 。
  • 步骤 2) 7 是奇数,减 1 得到 6 。
  • 步骤 3) 6 是偶数,除以 2 得到 3 。
  • 步骤 4) 3 是奇数,减 1 得到 2 。
  • 步骤 5) 2 是偶数,除以 2 得到 1 。
  • 步骤 6) 1 是奇数,减 1 得到 0 。

示例 2:

输入: num = 8 输出: 4

解释:

  • 步骤 1) 8 是偶数,除以 2 得到 4 。
  • 步骤 2) 4 是偶数,除以 2 得到 2 。
  • 步骤 3) 2 是偶数,除以 2 得到 1 。
  • 步骤 4) 1 是奇数,减 1 得到 0 。

示例 3:

输入: num = 123 输出: 12

提示:

  • 0 <= num <= 10^6

思考探究

经过研究,其中其实隐藏了规则

  1. 如果这个数字是2的n次方,那么他的题目步数其实就是log2(log2为底),也就是求这个n是多少。
  2. 如果不是2的n次方,那么其实就只能按照常规方案计算,奇数减一,偶数除2。
  3. 为了运算正常,其中会有大量的取模运算,为了判断是技术还是偶数

那么这里我考虑到这里就非常适合使用位运算来计算,可以解决2个需要运算耗时的地方

  1. Q: 判断数字是不是2的n次方,只有等于了才能去计算这个n是多少

A:使用 (num & (num - 1)) == 0 来判断是否是2的n次方

因为如果数字是2的n次方,那么他的二进制必然是类似 00010000,只会存在一个1,然后num-1 就会变成 00001111,两边通过&运算,一定是没有相同的1,所以结果一定为0,就可以判断是否是2的n次方。

假设 num = 16:

16&16-1 = 00010000 & 00001111 = 0

假设 num = 15:

15&15-1 = 00001111 & 00001110 = 14

  • Q:对当前数字取模,判断是奇数还是偶数?

A: 使用(num & 1) == 0 判断是奇数还是偶数,相当于替换 num % 2 == 0 。这里的原因其实是判断奇数还是偶数的核心就是数字二进制的最后一位是什么,如果是奇数,一定是1。

那么判断奇数偶数的问题就转变成了判断二进制最后一位是0还是1的问题。

这样通过二进制的 & 操作就可帮助我们快速判断是基数还是偶数。

假设 num = 16:

16&1 = 00010000 & 00000001 = 0

假设 num = 15:

15&1 = 00001111 & 00000001 = 1

实践样例

代码

  public int numberOfSteps(int num) {
        int count = 0;
        while (num != 0) {
            if ((num & (num - 1)) == 0) {
                double v = Math.log(num) / Math.log(2);
                return (int) v + 1 + count;
            }
            
            //偶数
            if ((num & 1) == 0) {
                num = num / 2;
                count++;
            }

            //奇数
            if ((num & 1) != 0) {
                num = num - 1;
                count++;
            }
        }
        return count;
    }

结果

执行效率和内存还是很不错的,执行时间约等于1ms,内存使用率低于百分之97的java提交,对比与官方题解,我这种方案认为也还可以。

结尾

按照上述的方案就是针对位运算的一点小技巧,因为其实在常规的开发中,我们其实是不太会用位运算,原因我大概理解为以下几个

  1. 阅读位运算代码成本较高
  2. 业务上还不至于在这种细节点扣
  3. 位运算代码书写不够熟练

但个人理解,如果能一点一点开始位运算的话,可以在需要的时候尽可能的增加计算效率并且做leetCode的时候比较有用。

作者介绍

  • 某大厂高级软件工程师,代码爱好者
  • 定期分享技术相关,热点实时,计算机实用技巧
  • 有问题可在评论区回复或者联系我,用专业,程序员的思维带给你不一样的认知

欢迎点赞,关注博主,未来会有更多知识解答,有问题可以评论区讨论,尽全力帮大家解答。