算法锦囊9:一文搞定位运算

550 阅读4分钟

这是我参与更文挑战的第17天,活动详情查看: 更文挑战

最近想把自己刷算法题的经验心得整理一下,一方面为了复习巩固,另一方面也希望我的分享能够帮助到更多在学习算法的朋友。

专栏名称叫《算法锦囊》,在讲解算法时会注重整体性,但不会面面俱到,适合有一定算法经验的人阅读。

这一次我们重点来看位运算,这一部分的所有题目和源码都上传到了github的该目录下,题解主要用Python语言实现。

概述

很多题目如果能用位运算解答会很巧妙,位运算是技巧性非常高的解法。

我整理了一些位运算当中常用到的知识点。

  • 判断奇偶:x%2==1 —>(x&1)==1 x%2==0 —>(x&1)==0
  • x>>1等价于x/2
  • X=X&(X-1)清零最低位的1
  • X&-X得到最低位的 1
  • X&~X=>0

191. 位1的个数

题目地址为 191. 位1的个数

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

示例 1:

输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'

思路1,直接获取二级制字符串并进行统计。

class Solution:
    def hammingWeight(self, n: int) -> int:
        return bin(n).count("1")

思路2,巧妙利用位运算,效率更高。

class Solution:
    def hammingWeight(self, n: int) -> int:
        count = 0
        while n > 0:
            n = n & (n-1)
            count += 1
        return count

231. 2的幂

题目地址为 231. 2的幂

给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。

如果存在一个整数 x 使得 n == 2x ,则认为 n 是 2 的幂次方。

示例 1:

输入:n = 1
输出:true
解释:20 = 1

如果用传统方法,则需要进行迭代。

class Solution:
    def isPowerOfTwo(self, n: int) -> bool:
        if n == 0:
            return False
        while n % 2 == 0:
            n //= 2
        return n == 1

也可以利用位运算的特性,如果是2的幂,那么和比它小1的数的与为0。

class Solution:
    def isPowerOfTwo(self, n: int) -> bool:
        if n == 0: return False
        return n & (n-1) == 0

338. 比特位计数

题目地址为 338. 比特位计数

给定一个非负整数 num。对于 0inum 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。

示例 1:

输入: 2
输出: [0,1,1]

传统解法就是每个数都去统计1的个数。

class Solution:
    def countBits(self, num: int) -> List[int]:
        def countOnes(x: int) -> int:
            ones = 0
            while x > 0:
                x &= (x - 1)
                ones += 1
            return ones
        
        bits = [countOnes(i) for i in range(num + 1)]
        return bits

这题也可以利用动态规划来做,不过比较难想。

还记得我们可以利用 X&(X-1)来清除末尾的1吗,我们可以找到递归关系式:

bits[x]=bits[x&(x−1)] + 1

class Solution:
    def countBits(self, num: int) -> List[int]:
        bits = [0]
        for i in range(1, num + 1):
            bits.append(bits[i&(i-1)] + 1)
        return bits

136. 只出现一次的数字

题目地址为 136. 只出现一次的数字,是一道面试高频题

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

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:

输入: [2,2,1]
输出: 1

这一题比较直观的做法是,借助于列表或者哈希表来辅助计算,但这样引入了额外的空间。

我们可以通过位运算的思路来巧妙地解答本题,两个相同的数的异或结果为0。

class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        for i in range(1, len(nums)):
            nums[i] ^= nums[i-1]
        return nums[-1]

还可以利用Python里的reduce高阶函数,一行完成。

from functools import reduce
class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        return reduce(lambda x, y: x ^ y, nums)

168. Excel表列名称

最后,我们来看 168. Excel表列名称,这道题严格来讲不属于位运算,但我们可以从中窥探出进制转换的技巧。

给定一个正整数,返回它在 Excel 表中相对应的列名称。

例如,

    1 -> A
    2 -> B
    3 -> C
    ...
    26 -> Z
    27 -> AA
    28 -> AB 
    ...
示例 1:

输入: 1
输出: "A"

这道题需要对被除数进行-1的处理,才能得到对应的字符。

image.png

class Solution:
    def convertToTitle(self, n: int) -> str:
        s = ''
        while n:
            n -= 1
            s = chr(65 + n % 26) + s
            n //= 26
        return s

如果反过来从列名称反推序号,可看171. Excel表列序号

class Solution:
    def titleToNumber(self, columnTitle: str) -> int:
        return sum([(ord(columnTitle[i]) - ord('A') + 1)*26**(len(columnTitle)-i-1) for i in range(len(columnTitle))])