位运算分治(LeetCode颠倒二进制位题目总结)

力扣题目描述:


 

问题描述

        一开始接触到二进制有关的运算什么与、或、异或等,每次都去看题解很不甘心,决心花一些时间整理一下,虽然这道题目有很多的解法,但主要记录二进制分治,只因其空间时间复杂度均为O(1),何况Java的int类型包装类中就是试用了二进制分治,这还是值得一学。

要彻底弄懂二进制分治,首先得了解这几个运算符合运算特性:&,|,&&,||;这几个的却别

        &:通路与,是一个二元运算符,其运算规则是 x & 1 = x, x & 0 = 0;就拿a & b 来说,先判断a的值不论是否为真假,还要判断b的真假。两个为真结果才为真。而&&的区别就在于,&&是短路与,例如 a && b,只要a为假,就立即返回假,不会去判断b,而a为真是才去判断b是否为真。x | 0 = xx | 1 = 1,其思路也和上面差不多;

        官方的分治是这样写的,可是为什么这样写,官方也没有太多的解释,接下来我们仔细研究其原理和步骤。

public class Solution {
    private static final int M1 = 0x55555555; // 01010101010101010101010101010101
    private static final int M2 = 0x33333333; // 00110011001100110011001100110011
    private static final int M4 = 0x0f0f0f0f; // 00001111000011110000111100001111
    private static final int M8 = 0x00ff00ff; // 00000000111111110000000011111111

    public int reverseBits(int n) {
        n = n >>> 1 & M1 | (n & M1) << 1;
        n = n >>> 2 & M2 | (n & M2) << 2;
        n = n >>> 4 & M4 | (n & M4) << 4;
        n = n >>> 8 & M8 | (n & M8) << 8;
        return n >>> 16 | n << 16;
    }
}


原因分析:

1、M1~M4如何得来?为什么是这几个数?别的可不可以?

       回答这几个问题之前我们得了解二进分治思想:这里额交换必须是1,2,4,8,16……

                简单来说其实就是:1位交换(奇偶数位交换)

                                                 2位交换

                                                 4位交换

                                                    ……

                终止条件是位数的一半,例如以上代码块中,32位二进制数终止条件就是16,只需要进行到16位的交换即可,二进制用移位实现。

        我们用大家最熟悉的十进制模拟一下,看看能不能颠倒数字位。

所以到这里知道M1~M4的取值了吧,就是2的n次方(n==位数的一半,前半部分与后半部分进行一次交换)

所以官方的答案改成这样,也没有问题

public class Solution {

    private static final int M1 = 0x55555555; // 01010101010101010101010101010101
    private static final int M2 = 0x33333333; // 00110011001100110011001100110011
    private static final int M4 = 0x0f0f0f0f; // 00001111000011110000111100001111
    private static final int M8 = 0x00ff00ff; // 00000000111111110000000011111111
    private static final int M16 = 0xffffffff;// 11111111111111111111111111111111

    public int reverseBits(int n) {
        n = (n >>> 1) & M1   | (n & M1) << 1;
        n = (n >>> 2) & M2   | (n & M2) << 2;
        n = (n >>> 4) & M4   | (n & M4) << 4;
        n = (n >>> 8) & M8   | (n & M8) << 8;
        n = (n >>> 16) & M16 | (n & M16) << 16;
        return n;
    }
}

         

  • 现在我们来解读第一句代码:n = (n & 0x55555555) << 1 | ((n >> 1) & 0x55555555);

   0x55555555变成二进制是0101 0101 0101 ...,其他的代码块上有,我就只拿这一个来分析。n & 0x55555555是取其奇数位,为什么呢?是不是忘记了&计算规则,x & 1 = x,x & 0 = 0;说来这么多,还不如画个图来得简单!

为其方便性我取4位做了分析


 

 

因为这是4位,故需要再交换一次2位就完成颠倒,我就不画了,有兴趣的小伙伴可以画画加深印象。 

画完后就会发现思路其实很简单:

  1. 保留奇数位,偶数位置零(否者不为0,在奇数位移动到偶数位时,偶数位不为零的又移动到了奇数位影响后面|运算),移动奇数位置偶数位(左移一位)
  2.  移动偶数位置奇数位再一次保留奇数位,偶数位置零
  3. 将以上两个步骤的值异或 |,即可完成一位的交换(奇偶数位交换)

        从自顶向上的角度看,我们把整个序列的翻转分成了:隔1位交换二进制数、隔2位交换二进制数、隔4位交换二进制数、隔8位交换二进制数、隔16位交换二进制数五个子问题,属于分治法思想。

 public int reverseBits(int n) {
        逐一移位
        int res = 0;
         for (int i = 0; i < 32 && n != 0; i++) {
            res |= (n & 1) << (31 - i);
           n = n >>> 1;
        }
        return res;

  

拓展:

统计1的数:第一步保留奇数位偶数位置零,偶数位移动至奇数位,再次保留奇数位,偶数位置零,合并。(合并最低位的1)

uint32_t getNum(uint32_t n) {
    n = (n & 0x55555555) + ((n >> 1) & 0x55555555);
    n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
    n = (n & 0x0f0f0f0f) + ((n >> 4) & 0x0f0f0f0f);
    n = (n & 0x00ff00ff) + ((n >> 8) & 0x00ff00ff);
    n = (n & 0x0000ffff) + ((n >> 16) & 0x0000ffff);

    return n;
}

参考博客:https://blog.csdn.net/qq_40950183/article/details/117389310

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

明天码上有钱啦

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

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

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

打赏作者

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

抵扣说明:

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

余额充值