leetcode刷题记录day023:190和461

190、难度简单:

提示:在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在示例 2中,输入表示有符号整数 -3,输出表示有符号整数 -1073741825

方法一:逐位颠倒:时间复杂度:O(logn) 空间复杂度:O(1)

原理:将 n 视作一个长为 32 的二进制串,从低位往高位枚举 n 的每一位,将其倒序添加到翻转结果 rev 中。
代码实现中,每枚举一位就将 n 右移一位,这样当前 n 的最低位就是我们要枚举的比特位。当 n 为 0 时即可结束循环。
需要注意的是,在某些语言(如 Java)中,没有无符号整数类型,因此对 n 的右移操作应使用逻辑右移。

public class Solution {
    public int reverseBits(int n) {
        int rev = 0;
        for (int i = 0; i < 32 && n != 0; ++i) {
            // |= 运算符和 += 这一类的运算符一样,拆解开就是 a = a | b
            // &两数都为1才返回1,否则0,而十进制1的二进制可以看做00..001
            // 所以n&1的效果就是把n的最低位和1做&运算(高位若为1那和0运算得0,若为0还得0)
            // 也就是n的最低位为1就返回1,为0就返回0
            // 31-i是指从第32位开始逐次-1,达到逆序的目的,也就是对rev从最高位开始加数
            // (n & 1) << (31 - i)意为将n的最低位带着数值提升到逆序时对称的位置
            // 然后和作为结果的rev做 | 运算并把结果赋值给rev
            // 用 | 是因为(n & 1) << (31 - i)是一个32位的只有一位是1或全为0的数,而rev是一个既有1也有0的数
            // 为了防止二者 | 运算时破坏rev结构,所以采用 | 。rev中还未添加的部分一定是0,
            // 而(n & 1) << (31 - i)包含了rev要添加位置的信息,若为1,那么0|1=1,否则0|0=0
		   rev |= (n & 1) << (31 - i);
            // 去掉n当前最低位
            n >>>= 1;
        }
        return rev;
    }
}
方法二:位运算分治:时空复杂度:O(1) :该方法是 Integer源码中的reverse方法

若要翻转一个二进制串,可以将其均分成左右两部分,再对划分出的左右部分各自进行划分左右,直至每部分只有一个元素
(对每部分递归执行翻转操作)
然后将左半部分拼在右半部分的后面,即完成了翻转
(例如12,左半放右半后面变为21就是翻转;1234,先划分左右,到各区域仅1个元素后开始返回,第一次返回2143,第二次返回4321,翻转完成)
由于左右两部分的计算方式是相似的,利用位掩码和位移运算,我们可以自底向上地完成这一分治流程。

对于递归的最底层(一位一组),我们需要交换所有奇偶位:
1、取出所有奇数位和偶数位;
2、将奇数位移到偶数位上,偶数位移到奇数位上。
类似地,对于倒数第二层(每两位分一组),按组号取出所有奇数组和偶数组,然后将奇数组移到偶数组上,偶数组移到奇数组上。以此类推。
需要注意的是,在某些语言(如 Java)中,没有无符号整数类型,因此对 n 的右移操作应使用逻辑右移。

翻转原理:以1234为例子:翻转过来就是把最左侧的数和最右的数交换,然后向内进一层再重复交换直至内层只剩一个数或没有数。
而1234翻转得到4321,只看区域变化就是原先的左半部分和右半分交换了位置,先保留这条性质,如果只是这么交换得到的是3412,此时和正确的翻转有差异,因为左半区域和右半区域各自区域内没有进行左右交换。而把这个顺序逆过来,先对总区域划分到左右区域各只有一个元素,然后交换,再逐层往上交换,就是翻转原理。

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;
    }
}

代码原理:各行原理相同,以 n = n >>> 1 & M1 | (n & M1) << 1; 为例:执行顺序为
含义为将 n >>> 1 & M1 的结果和 (n & M1) << 1 的结果进行 | 运算,然后赋值给 n
n >>> 1 & M1 意为将 n >>> 1 的结果和 M1 做 & 运算,| 运算右侧的同理

第一行代码的效果:每两位互换
(n & M1) << 1:取奇位数并左移一位
(因为M1只有奇数位才使1,而&运算规定只有两1的才能为1,而偶数为全为0会使二者运算结果只保留奇数位的原值,偶数位全为0)
n = n >>> 1 & M1:右移一位再取奇位数
(相当于把(n & M1) << 1没取到的偶数位都取到了)
二者做 | 运算,假设原数据为123456(为方便描述瞎取的,6为最低位),则左侧结果为204060,右侧结果为010305,二者做或运算(含1为1),正好左侧为0的位置右侧处不为0,运算完正好是两两换位,肉眼也能看出。

第二行代码:| 运算符左半部分效果:每四位,后两位移到前两位。右半部分效果:每四位,前两位移到后面两位
(n & M2) << 2:4位之中取前两位左移两位(与第一行的同理)
(i >>> 2) & 0x33333333 : 右移两位取前两位(与第一行的同理)

第三行代码:原理同第二行,每四位交换了一下
(n & M4) << 4:取前4位左移4位
n = n >>> 4 & M4:右移4位取前4位

第四行同理,这里略

461、难度简单:

方法一:内置位计数功能:时空复杂度:O(1)

汉明距离广泛应用于多个领域。在编码理论中用于错误检测,在信息论中量化字符串之间的差异。
两个整数之间的汉明距离是对应位置上数字不同的位数。
根据以上定义,我们使用异或运算,记为 ⊕,当且仅当输入位不同时输出为 1。

本题注意点:当比较如 4(0100)和 1(1)这种二进制最高位位数不同的情况时,我们需要考虑如何才能在 1 的前面把和 4 相差的 0 全都补充上:使用异或即可,比如 4⊕1 = 5 就可以证明。

计算 x 和 y 之间的汉明距离,可以先计算 x⊕y,然后统计结果中等于 1 的位数。

// 大多数编程语言都内置了计算二进制表达中 1 的数量的函数。在工程中,我们应该直接使用内置函数。
class Solution {
    public int hammingDistance(int x, int y) {
        return Integer.bitCount(x ^ y);
    }
}
方法二:移位实现位计数:时间复杂度:O(logC) 空间复杂度:O(1)

原理:记 s=x⊕y,我们可以不断地检查 s 的最低位,如果最低位为 1,那么令计数器加一,然后我们令 s 整体右移一位,这样 s 的最低位将被舍去,原本的次低位就变成了新的最低位。我们重复这个过程直到 s=0 为止。这样计数器中就累计了 s 的二进制表示中 1 的数量。

class Solution {
    public int hammingDistance(int x, int y) {
        int s = x ^ y, ret = 0;
        while (s != 0) {
            ret += s & 1;
            s >>= 1;
        }
        return ret;
    }
}
方法三:Brian Kernighan 算法:时空复杂度同方法二

原理:在方法二中,对于 s=(10001100)2 的情况,我们需要循环右移 8 次才能得到答案。而实际上如果我们可以跳过两个 1 之间的 0,直接对 1 进行计数,那么就只需要循环 3 次即可。
我们可以使用 Brian Kernighan 算法进行优化,具体地,该算法可以被描述为这样一个结论:记 f(x) 表示 x 和 x-1 进行与运算所得的结果(即 f(x)=x & (x−1)),那么 f(x) 恰为 x 删去其二进制表示中最右侧的 1 的结果
在这里插入图片描述

基于该算法,当我们计算出 s=x⊕y,只需要不断让 s = f(s),直到 s=0 即可。这样每循环一次,s 都会删去其二进制表示中最右侧的 1,最终循环的次数即为 s 的二进制表示中 1 的数量。

在这里插入图片描述

class Solution {
    public int hammingDistance(int x, int y) {
        int s = x ^ y, ret = 0;
        while (s != 0) {
            s &= s - 1;
            ret++;
        }
        return ret;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

CodeYello

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

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

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

打赏作者

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

抵扣说明:

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

余额充值