巧用位运算

176 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情

位运算符

Java中给我们提供了很多位运算符,主要有以下内容

标题
<<左移左移一位相当于该数乘以2
>>右移右移一位相当于该数除以2
&有0则0
|有1则1
~1变0,0变1
异或相同为0,不同为1

虽然位运算在平时用的比较少,但是在某些场景下使用起来还是很方便

使用场景

1.判断一个数是奇数还是偶数

一般情况下这种场景我们都是使用i%2==0来判断i的奇偶性,但是这里我们也可以使用与(&)运算符 来判断,因为一个数的二进制表示中,从低位到高位,只有第一位会决定这个数的奇偶性(如果第一位为1,则20=12^0=1即该数为奇数,否则为0,该数为偶数),所以我们可以用该数和1进行与运算,就可以判断出结果

x & 1 == 1则为奇数
x & 1 == 0则为偶数

2.求一个数二进制中位为1的个数

编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数

根据按位与的性质,只有当前位置运算的都是1,结果才是1,所以我们只需要将32个位置分别为1的时候和n进行运算,如果结果为0,说明当前二进制位置是0,否则为1,代码如下:

fun hammingWeight(n: Int): Int {
    var count = 0
    for (index in 0 until 32) {
        if (((1 shl index) and n) != 0) {
            count++
        }
    }
    return count
}

还有个比较有意思的做法,可以直接消除二进制末尾的1,那就是n&(n-1),这个代码可以将二进制中最后一个1变成0,那么我们可以用此性质,判断将一个数二进制位全部变成0所需要的次数,也就是题目的答案,即

fun hammingWeight(n: Int): Int {
    var count = 0
    var num = n
    while (num!=0){
        count++
        num = num and (num-1)
    }
    return count
}

3. 只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

拿到这道题最先想到的就是遍历数组,然后使用HashMap存储遍历过的数据,key为数字,value为出现的次数,最后遍历HashMap获取value==1key就是结果

fun singleNumber(nums: IntArray): Int {
    val temp = HashMap<Int, Int>()
    for (num in nums) {
        val value = temp[num]
        if (value == null) {
            temp[num] = 1
        } else {
            temp[num] = value + 1
        }
    }
    var result = -1
    temp.forEach { (k, v) ->
        if (v == 1) {
            result = k
        }
    }
    return result
}

这样虽然可以求解,但是需要O(N)的额外空间,如果使用位运算,则可以做到O(1)的空间复杂度,对于这道题,我们可以使用异或运算,因为异或运算满足以下性质

  1. x^0=x 即0异或任何数都是原来的数
  2. x^x=0 即任何数异或本身结果都是0
  3. a^b^c=(a^b)^c=a^(b^c)=b^(a^c) 即异或满足交换律和结合律

由题目可知,除了一个数字出现一次,其余都出现2次,那么我们可以将出现2次的数字先分别进行异或然后再与只出现一次的数字异或,异或性质2告诉我们, x^x=0,那么出现2次的数字异或结果都是0,公式就变成了0^0^...0^0^x(x为只出现一次的数字),异或性质1告诉我们x^0=x,所以公式的运算结果就只有x了,即题目答案,代码如下

fun singleNumber(nums: IntArray): Int {
    var result=0
    for (num in nums) {
        result = result xor  num
    }
    return result
}