美文网首页
位运算运用的技巧

位运算运用的技巧

作者: Robin92 | 来源:发表于2020-05-07 23:56 被阅读0次

Go 中的位运算符

  • & 按位与
  • ^ 一元操作为非,二元操作为异或
  • | 按位或
  • &^ 清零操作,为二元操作,a&^b 等同于 a&(^b)
  • << 左移位操作
  • >> 右移位操作

注意,go 中是没有 >>> 操作的,这个操作在 java 中为带符号移位操作。

移位操作的特点

  • 速度快
  • 安全,防溢出

安全的特点:比如在算 a = b * 2 时,若 b 很大,可能导致溢出报错,但用 a = b << 1 的方式的话,则不会报错。但你可能会发现这个计算结果不正确,并不是真正的 2 * b,这个点需要注意。

取反操作的妙用

计算类型中的最大最小值

    // 求无符号数的最大值
    max_uint := ^uint(0)     // 18446744073709551615
    max_uint8 := ^uint8(0)   // 255
    max_uint16 := ^uint16(0) // 65535
    max_uint32 := ^uint32(0) // 4294967295
    max_uint64 := ^uint64(0) // 18446744073709551615
    fmt.Println(max_uint, max_uint8, max_uint16, max_uint32, max_uint64)
    // 有符号数取反后被机器认为是补码,所以 ^0 皆为 -1
    f := ^int(0) // -1,int8, int16, int32, int64 皆一样
    fmt.Println(f)
    // 有符号数的最大值可以这样取(移位是带符号的)
    max_int := int(^uint(0) >> 1)       // 9223372036854775807
    max_int8 := int8(^uint8(0) >> 1)    // 127
    max_int16 := int16(^uint16(0) >> 1) // 32767
    max_int32 := int32(^uint32(0) >> 1) // 2147483647
    max_int64 := int64(^uint64(0) >> 1) // 9223372036854775807
    fmt.Println(max_int, max_int8, max_int16, max_int32, max_int64)
    // 有符号数的最小值可以这样算(利用溢出)
    min_int := max_int + 1     // -9223372036854775808
    min_int8 := max_int8 + 1   // -128
    min_int16 := max_int16 + 1 // -32768
    min_int32 := max_int32 + 1 // -2147483648
    min_int64 := max_int64 + 1 // -9223372036854775808
    fmt.Println(min_int, min_int8, min_int16, min_int32, min_int64)
    // 但注意,不可以这样算
    // wrong_example := int(^uint(0)>>1) + 1
    // 报错:constant 9223372036854775808 overflows int
    // 差别是没有用中间变量进行运算,而是直接使用的常数值进行的计算

    // 最小值也可以这样算,但注意这也是在常数上的操作,右移位数超出范围了也会报上方错误
    min_int_2 := int(-1) << 63 // -9223372036854775808
    fmt.Println(min_int_2)

求一个基本数值类型的最小值,也可以从二进制码入手,我们知道一个有符号数值的最小值为 “符号位为 1,其余位皆为 0”(补码形式),所以可以 用一个全 1 的数整体向右移一位,首位补 0,再整体取反,来得到这个二进制码。由于 Go 中移位是带符号的,所以前面需要是不带符号类型,后面需要是带符号的类型。如

    // 另一种方式算得有符号数的最小值
    a := ^(^uint(0) >> 1) // -9223372036854775808
    fmt.Println(int(a))

提取一个无符号数的二进制形式的最右侧的 1

比如,一个数的二进制数 a 为 11011000,即要得出 00001000 这个数。

这个算法题可以先将 a 取反,则为 00100111,然后加 1,得到 00101000,我们对比一下:

11011000 // a
00100111 // ^a
00101000 // ^a+1
00001000 // a & (^a+1)

此时可以明显得看到 a&(^a+1) 就是我们所要的结果。

异或的妙用

首先来说说异或运算的性质

  • 0^N == N
  • N^N == 0
  • 异或满足交换律、结合律

由以上三个性质,我们来介绍下异或的几个算法题:

  • 找到一组数据中唯一出现奇数次的数
  • 不用额外的变量实现两数交换
  • 找到一组数据中有且仅有的两个出现奇数次的数

找到一组数据中唯一出现奇数次的数

由于异或有上述三条性质,所以可以有这样的推导公式:

a^b^c^b^a == (a^a)^(b^b)^c == 0^0^c == c

显然,在一组数据中有且仅有一个奇数次数据时,可以用它进行运算找出。

不用额外的变量实现两数交换

另外,异或上述的三条性质可以实现 不需要额外的变量即可实现两数交换,以下是证明过程:

    a := 345
    b := 564
    a = a ^ b         // a' => a^b
    b = a ^ b         // b' => a'^b == (a^b)^b == a^(b^b) == a
    a = b ^ a         // a'' => b'^a' == a^a^b == b
    fmt.Println(a, b) // 564 345

找到一组数据中有且仅有的两个出现奇数次的数

上面可以完成找出一组数据中有且仅有一个奇数次的数,但如何找出两个呢?

我们上面介绍了,可以通过数据全部异或,得到一个结果 A,其中出现偶数次的数相互异或后结果为 0,所以可以得出结论,这个结果 A 的某一个 bit 位是 1 的原因肯定是两个奇数次数值的 bit 位不一致引起的。

因为所有数它们的某一个 bit 位非 1 即 0,所以找结果 A 的任意一个为 1 的 bit 位,可以将整组数据分为两组:一组数据此 bit 位值为 1;另一组数据此 bit 位为 0。还可以得到结论:此 bit 位为 1 的数中,有且仅有一个数出现了奇数次;此 bit 位为 0 的数中,有且仅有一个数出现了奇数次。

于是,这这问题就回到了 有且仅有一个元素出现了奇数次 的问题上了。

所以此答案是这样的:

// 找出一组数据中有且仅有两个出现奇数次的数
func getTwoOddValue(arr []uint64) (uint64, uint64) {
    // 1 得到全部异或的结果
    xor := uint64(0)
    for _, e := range arr {
        xor ^= e
    }
    // 2 按 xor 的某一个 bit 位的值为 1 的位找出此元素
    // 2.1 找出此 bit 位为 1 的一个值
    singleBitVal := xor & (^xor + 1)
    // 2.2 找出此 bit 位为 1 的那个奇数次元素
    elem_1 := uint64(0)
    for _, e := range arr {
        if singleBitVal&e > 0 {
            elem_1 ^= e
        }
    }
    // 3 找出另一个元素
    elem_2 := elem_1 ^ xor
    return elem_1, elem_2
}

总结,这里介绍了位操作的一些用法。位操作是性能相当快的操作,而且它还有一些其他的妙用,这些在算法题中常常遇到。本文还介绍了几个简单的算法题:

  • 找类型(无/有符号)的最大/小值
  • 提取一个无符号数二进制形式的最右侧的 1
  • 不用额外的空间实现两数交换
  • 找出一组数据中唯一出现奇数次的一个数
  • 找出一组数据中有且仅有两个出现奇数次的两个数

相关文章

  • 位运算运用的技巧

    Go 中的位运算符 & 按位与 ^ 一元操作为非,二元操作为异或 | 按位或 &^ 清零操作,为二元操作,a&^b...

  • 位运算的运用

    前言 从现代计算机电路来说,只有高电平/低电平两种状态,即为0/1状态,计算机中所有的数据按照具体的编码格式以二进...

  • iOS 位运算的运用

    枚举类型的设计: 设置类型可以是 MediaType_Text | MediaType_Photo | Med...

  • 位运算技巧

    消除x最后一位1:x & (x - 1)Go代码: 一、用O(1) 时间检测整数 n 是否是 2 的幂次。分析:如...

  • 位运算技巧

    基础知识 对于位运算,大家都很熟悉,基本的位操作有与(&)、或(|)、非(~)、异或(^)等等。在面试中经常会出现...

  • 位运算技巧

    位运算技巧的总结 1. 位运算基础 与(&)两个比特位同时为1结果为1,否则为0 或(|)只要有一个为1结果就为1...

  • 巧妙运用C语言位运算

    巧妙运用C语言位运算,C语言是面向过程的,而C++是面向对象的 位运算 位运算的运算分量只能是整型或字符型数据,位...

  • 位运算的技巧

    位运算的技巧 基本 and 运算 通常用于二进制取位操作。 例如,and 1就是取二进制末位,可以用来判断一个数的...

  • Python基础之位运算符(含原码反码补码的通俗解释)

    目录 1 二进制 2 原码、反码、补码 3 位运算符 4 位运算符使用技巧 上回学习运算符时,漏了位运算符,因为位...

  • Javascript 位运算及运用

    Javascript 位运算 参考:巧用JS位运算 ECMAScript 整数有两种类型,即有符号整数(允许用正数...

网友评论

      本文标题:位运算运用的技巧

      本文链接:https://www.haomeiwen.com/subject/wuuzghtx.html