• 每日一道 LeetCode (43):翻转二进制数


    每天 3 分钟,走上算法的逆袭之路。

    前文合集

    每日一道 LeetCode 前文合集

    代码仓库

    GitHub: https://github.com/meteor1993/LeetCode

    Gitee: https://gitee.com/inwsy/LeetCode

    题目:翻转二进制数

    题目来源:https://leetcode-cn.com/problems/reverse-bits/

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

    示例 1:

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

    示例 2:

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

    提示:

    • 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
    • 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 2 中,输入表示有符号整数 -3,输出表示有符号整数 -1073741825。

    解题方案一:暴力翻转

    常规的线型思维是拿到一个数,直接翻转就好了,比如下面这样:

    这么干看起来简单,实际上并不是那么好实现。

    • 比如一个数 001 ,我们翻转以后的结果是 1 ,并不会是 001 ,前面的 0 会省去。
    • 这种方案还需要考虑是否会整形溢出, Java 的整数溢出后的二进制数会表示成负数。

    这就很坑了,由于是二进制数,解决方案可以使用移位运算,而不是除法运算。

    先定义一个结果 res 为 0 ,然后对 110 进行翻转操作,把每一步的结果都存在 res 中,最后得到的 res 就是我们想要的结果。

    第一步:
    res 左移一位,res = 0
    110 的最低位为 0 ,加到 res 上,结果为 0 
    110 右移一位,结果为 11
    
    第二步:
    res 左移一位,res = 0
    11 的最低位为 1 ,加到 res 上,结果为 1
    11 右移一位,结果为 1
    
    第三步:
    res 左移一位,res = 10
    1 的最低位为 1 ,加到 res 上,结果为 11
    1 右移一位,结果为 0 ,操作结束
    

    第一个问题来了,如何获取 n 的最低位?

    可以使用位运算的 & ,使用 (n & 1) 可以得到 n 的最低位,原理的话我就不说了,看不懂的可以查查位运算 & 是如何计算的。

    public int reverseBits(int n) {
        int res = 0;
        for (int i = 0; i < 32; i++) {
            // 获取 n 的最低位加到 res 上
            res = (res << 1) + (n & 1);
            // n 右移一位
            n >>= 1;
        }
        return res;
    }
    

    这感觉第一步就结束了啊,直接超过 100% 了,不管那么多,接着往下看答案还是。

    解题方案二:分治合并

    答案上这个方案简直秀到家了,从第一眼看一脸懵逼,到后面打断点一步一步看下来赞叹不已,只留下大写的 「牛逼」 两个字。

    先放代码,我觉得大多数人第一眼看到代码的人都是一脸懵。

    public int reverseBits_1(int n) {
        n = ((n & 0xffff0000) >>> 16) | ((n & 0x0000ffff) << 16);
        n = ((n & 0xff00ff00) >>> 8) | ((n & 0x00ff00ff) << 8);
        n = ((n & 0xf0f0f0f0) >>> 4) | ((n & 0x0f0f0f0f) << 4);
        n = ((n & 0xcccccccc) >>> 2) | ((n & 0x33333333) << 2);
        n = ((n & 0xaaaaaaaa) >>> 1) | ((n & 0x55555555) << 1);
        return n;
    }
    

    看到这段代码我第一个感觉是:我是谁?我在哪?我去哪?

    人称三大哲学问题。

    这个解题思路是这样的:

    • 总共 32 个数,先把这 32 个数从中间拆分成 2 组,每组 16 个数,然后反转这两组进行合并。
    • 然后再把 16 个数拆分成 2 组,每组 8 个数,再反转这两组合并。
    • 接着进行查分,直至拆分成每组 1 个数,然后进行反转合并。

    我知道,配合这段解释大多数人还是看不懂,我就用题上的一个数举例子吧,人肉帮你们 debug 一下:

    第一次拆分,是通过两个 16 进制的数进行的,分别是 0xffff00000x0000ffff ,这两个数如果转换成 2 进制的结果如下:

    0b11111111_11111111_00000000_00000000
    0b00000000_00000000_11111111_11111111
    

    实际上第二个数前面的 0 是应该省略掉的,方便理解我手动补 0 。

    • 第一次进行了位运算的 & 运算,相当于直接取到了左边的 16 个数字和右边的 16 个数字。
    • 然后对得到的结果分别进行位移操作,左边的结果进行右移 16 位,右边的结果左移 16 位。
    • 接下来使用 | 将左右两边的结果合并起来。
    • 使用一些有规律的 16 进制的数,分别取到左右 8 位,左右 4 位,左右 2 位 和左右 1 位,进行反转合并。
    • 重复这个过程,最终就得到了整个二进制数的反转后的结果。

    注意: >>> 在 Java 的含义是无符号位移,无论是正数还是负数,高位通通补 0 。

    这是一个无需循环就能原地翻转二进制数的方案,真心是服了啊,不过感觉在如果是在面试中出这道题,进行 & 操作的时候这些 16 进制数不大好写,直接写 2 进制可能来的更为简单一点。

    扫描二维码关注「极客挖掘机」公众号!
    作者:极客挖掘机
    定期发表作者的思考:技术、产品、运营、自我提升等。

    本文版权归作者极客挖掘机和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
    如果您觉得作者的文章对您有帮助,就来作者个人小站逛逛吧:极客挖掘机
  • 相关阅读:
    mac下的一个类似“_kbhit()”实现
    mac使用备注
    open()打开文件失败对应的各种错误码
    xcode显示行号show gutter
    下载google code中源码的几个工具
    HTML5迷你游戏作验证码
    Linux+eclipse+gdb调试postgresql源码
    S-Nim
    【求助】从大表中删除小表中存在的记录问题
    Binary Tree Zigzag Level Order Traversal (LeetCode) 层序遍历二叉树
  • 原文地址:https://www.cnblogs.com/babycomeon/p/13649712.html
Copyright © 2020-2023  润新知