日常系列LeetCode《6·位运算篇》

Posted 常某某的好奇心

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了日常系列LeetCode《6·位运算篇》相关的知识,希望对你有一定的参考价值。

数据规模->时间复杂度

<=10^4 😮(n^2)
<=10^7:o(nlogn)
<=10^8:o(n)
10^8<=:o(logn),o(1)

基础:


注意:
正整数的补码=反码=原码
负整数的补码=反码+1=~原码+1







技巧:




lc 191【剑指 15】 :https://leetcode.cn/problems/number-of-1-bits/
提示:
输入必须是长度为 32 的 二进制串 。
进阶:
如果多次调用这个函数,你将如何优化你的算法?

#方案一:对每一位进行测试,看是否为1
class Solution:
    def hammingWeight(self, n: int) -> int:
        #o(1)
        res=0
        #<o(32)
        for i in range(1,33):
            if n&1<<(i-1) !=0:res+=1
        #
        return res
        
#方案二:不断右移n,判断最后一位是否为1
class Solution:
    def hammingWeight(self, n: int) -> int:
        #o(1)
        res=0
        #<o(32)
        for i in range(32):
            if n&1 !=0:
                res+=1
            n>>=1
        #
        return res
 
 #方案三:每次移除掉最后一个1,直至n为0
class Solution:
    def hammingWeight(self, n: int) -> int:
        #o(1)
        res=0
        #<o(32)
        while n !=0:
                n&=n-1
                res+=1
        #
        return res

lc 461 【top100】:https://leetcode.cn/problems/hamming-distance/
提示:
0 <= x, y <= 2^31 - 1

class Solution:
    def hammingDistance(self, x: int, y: int) -> int:
        #
        res=0
        s=x^y #异或
        while s!=0:
            s &=(s-1)#去1化
            res+=1
        #
        return res

lc 477 :https://leetcode.cn/problems/total-hamming-distance/
提示:
1 <= nums.length <= 10^4
0 <= nums[i] <= 10^9
给定输入的对应答案符合 32-bit 整数范围

#不同位的汉明距离是相互独立的
#我们考虑数组中每个数二进制的第i位,假设一共有t个1 和 n - t个0
#,那么显然在第i位的汉明距离的总和为t * (n - t)
class Solution:
    def totalHammingDistance(self, nums: List[int]) -> int:
        #
        n=len(nums)
        s_um=0
        counts=[0]*32  #统计各数字对应位上1的总个数
        #o(32*n)
        for num in nums:
            i=0
            while num!=0:
                if num&1!=0:#判断:最后一位是否1
                    counts[i]+=1
                i+=1#注意位置
                num>>=1
        #
        for s in counts:
            s_um+=s*(n-s)
        return s_um

lc 231 :https://leetcode.cn/problems/power-of-two/
提示:
-2^31 <= n <= 2^31 - 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
class Solution:
    def isPowerOfTwo(self, n: int) -> bool:
        if n==0:return False
        return n&(n-1)==0#去掉最后一位1     

lc 371 :https://leetcode.cn/problems/sum-of-two-integers/
提示:
-1000 <= a, b <= 1000

#异或运算:二进制的无进位加法
#与计算:计算出哪一位需要进位
class Solution:
    def getSum(self, a: int, b: int) -> int:
        #
        MAX=0x7FFFFFFF  #[0111 1111 1111 1111 1111 1111 1111 1111]最大正整数对应的二进制
        MASK=0xFFFFFFFF #将整数二进制位固定至32位
        #Python:整形二进制无固定位数
        while b !=0:
            a,b=(a^b)&MASK,((a&b)<<1)&MASK #非进位加法 #补进位
        #返回:正整数或将负数的补码转成负数
        return a if a<=MAX else ~(a^MASK) #key

lc 29【剑指 001】:https://leetcode.cn/problems/divide-two-integers/
提示:
被除数和除数均为 32 位有符号整数。
除数不为 0。
假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−2^31, 2^31 − 1]。本题中,如果除法结果溢出,则返回 2^31 − 1。

#方案一:超时
class Solution:
    def divide(self, dividend: int, divisor: int) -> int:
        a,b=dividend,divisor
        #注:边界
        if a==-2**31 and b==-1:return 2**31-1
        #o(n):减法代替除法,2**31-1->10^10(超时)
        if (a>0)^(b>0):sign=-1  
        else: sign=1
        #abs(-2**31)=-2**31(溢出)->(2**31-1)变负(不越界)
        # a=abs(dividend)
        # b=abs(divisor)
        if a>0:a=-a
        if b>0:b=-b 
        res=0
        while a<=b:
            a-=b
            res+=1
        #
        return res if sign==1 else -res

#方案二:优化-每次尝试减去除数的倍数
#超时
class Solution:
    def divide(self, dividend: int, divisor: int) -> int:
        a,b=dividend,divisor
        #① 注:边界
        if a==-2**31 and b==-1:return 2**31-1
        #② 符号
        if (a>0)^(b>0):sign=-1  
        else: sign=1
        #abs(-2**31)=-2**31(溢出)->(2**31-1)变负(不越界)
        #③ 技巧:全转负数
        if a>0:a=-a
        if b>0:b=-b
        #④ o(logn*logn):减去除数的倍数 a-b*2^k=0->k=log(a/b);a-2^t=0->t=log(a)
        res=0 
        while a<=b:
            value,k=b,1
            while value>=0xc0000000 and a<=value+value: #保证vaue>=-2^30(这用十六进制表示),不越界 
                k+=k
                value+=value
            a-=value
            res+=k
            
        #
        return res if sign==1 else -res

#方案三:再次优化:每次从最大位数开始尝试
#o(31)->o(1)
class Solution:
    def divide(self, dividend: int, divisor: int) -> int:
        a,b=dividend,divisor
        #① 注:边界
        if a==-2**31 and b==-1:return 2**31-1
        #② 符号
        sign=-1 if (a>0)^(b>0) else 1
        #注:abs(-2**31)=-2**31(溢出)
        a=abs(a)
        b=abs(b)
        #③ o(1)
        res=0 
        for i in range(31,-1,-1):
            if (a>>i)-b >= 0:
                a-=b<<i
                res+=1<<i 
            #a>=b<<i存在溢出越界问题 
            #无符号右移的目的是:将-2147483648看成2147483648
            #如果 b = -2147483648,那么(α >>> i) >= b 永远为 true,但是(a >>> i) - b >=0为false      
        #
        return res if sign==1 else -res

lc 136【top100】:https://leetcode.cn/problems/single-number/
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

#异或:类比哈希思想(有去除无添加)
class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        #key:异或
        #o(1)
        base=0
        #o(1)
        for num in nums:
            base^=num
        #
        return base

lc 137 【剑指 004】:https://leetcode.cn/problems/single-number-ii/
提示:
1 <= nums.length <= 3 * 10^4
-2^31 <= nums[i] <= 2^31 - 1
nums 中,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次
进阶:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

#方案一:异或代替哈希查找
class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        #o(n)
        one=twice=0
        for num in nums:
        	#one^num:one有则去除,无则结合twice情况是否增加
        	#~twice:无则,one增加
            one=(one^num)& ~twice
            twice=(twice^num) & ~one
        #
        return one

#方案二:统计每个数字指定位上1的个数
#如果1的个数不是3的倍数,说明那个只出现一次的数字的二进制位中在这一位是1 
class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        #o(n)
        res=0
        for i in range(32):
            #统计:第i位
            count=0
            count=sum((num>>i)&1 for num in nums) #num第i位是否有1
            #第i位的count
            if count %3 !=0:
                #注意:pothon 对【有符号整数类型】和【无符号整数类型】是没有区分,所以需要区分第 31 位,如果是 1 的话,那么需要减掉 -2^31  ?
                if i==31:
                    res -=1<<i
                else:res |= 1<<i
        #
        return res

lc 260:https://leetcode.cn/problems/single-number-iii/
提示:
2 <= nums.length <= 3 * 104
-231 <= nums[i] <= 231 - 1
除两个只出现一次的整数外,nums 中的其他数字都出现两次
进阶:你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?

#[1,1,5]和[2,2,3]:分别对两个数组所有数字异或[ 3,5]
class Solution:
    def singleNumber(self, nums: List[int]) -> List[int]:
        #
        mask=0
        for num in nums:
            mask ^=num 
        diff=mask&(-mask) #取末1:将两个只出现1次的数字划分到两组
        #o(1),o(n)
        res=[0]*2
        for num in nums:
            if num & diff !=0: 
                res[0]^=num
            else: res[1]^=num
        #
        return res

lc 1318 :https://leetcode.cn/problems/minimum-flips-to-make-a-or-b-equal-to-c/
提示:
1 <= a <= 10^9
1 <= b <= 10^9
1 <= c <= 10^9

class Solution:
    def minFlips(self, a: int, b: int, c: int) -> int:
        #o(1)
        diff=(a|b)^c #获得相异,需翻转的位
        #o(1)
        res=0
        if diff==0:return 0
        for i in range(32):
            #相异:第i位
            if (1<<i)&diff !=0:
                #只有一种需要两次翻转的情况
                if c&(1<<i)==0 and a&(1<<i)==b&(1<<i):
                    res+=2
                else:
                    res+=1
        #
        return res

lc 201 :https://leetcode.cn/problems/bitwise-and-of-numbers-range/
提示:
0 <= left <= right <= 2^31 - 1

#方案一:右移
class Solution:
    def rangeBitwiseAnd(self, left: int, right: int) -> int:
        #o(32):公共部分
        res=0
        while left!=right:
            left=left>>1
            right=right>>1
            res+=1
        #还原
        return left<<res
#方案二:去1
#不断的抹掉right的最后一个1,一直到right < left为止
class Solution:
    def rangeBitwiseAnd(self, left: int, right: int) -> int:
        #不断去掉最后一个1
        while left<right:
            right=right&(right-1)
        #
        return right

lc 476 :https://leetcode.cn/problems/number-complement/
提示:
1 <= num < 2^31
给定的整数num 保证在32位带符号整数的范围内
你可以假定二进制数不包含前导零位。

class Solution:
    def findComplement(self, num: int) -> int:
        #
        mask=~0 #0xFFFFFFFF
        #~mask:获得低位111
        while (mask & num) !=0: mask<<=1
        #
        return ~mask ^ num

lc 405 :https://leetcode.cn/problems/convert-a-number-to-hexadecimal/

class Solution:
    def toHex(self, num: int) -> str:
        if num==0:return '0'
        #
        chars='0123456789abcdef'
        res=''
        #
        #取后32位:python的整形二进制没有规定的位数
        #每4位做一次转换
        num=num & 0xFFFFFFFF
        while num!=0:
            res=chars[num&15]+res #15:0000 0000 0000 1111
            num>>=4
        #
        return res

lc 190:https://leetcode.cn/problems/reverse-bits/
提示:
输入是一个长度为 32 的二进制字符串
进阶: 如果多次调用这个函数,你将如何优化你的算法?

#方案一:迭代法
class Solution:
    def reverseBits(self, n: int) -> int:
        #o(32)
        res=0
        for i in range(32):
            res=(res<<1)|(n&1) #不断取最后一位并放于res
            n>>=1 #更新n
        #
        return res
        
#方案二:分治法
class Solution:
    def reverseBits(self, n: int) -> int:
        #
        m2=0x55555555  #0101 0101 0101 0101 ~
        m4=0x33333333  #0011 0011 0011 0011 ~
        m8=0x0f0f0f0f  #0000 1111 0000 1111 ~
        m16=0x00ff00ff #0000 0000 1111 1111 ~
        #o(5)
        # python 中没有 32 位的 int,我们需要将数字强转成 32 位 int
        n = (n >> 1) & m2 | (n & m2) << 1 & 0xffffffff #每两位交换
        n = ((n >> 2) & m4) | ((n & m4) << 2) & 0xffffffff
        n = ((n >> 4) & m8) | ((n & m8) << 4) & 0xffffffff
        n = ((n >> 8) & m16) | ((n & m16) << 8) & 0xffffffff
        #
        return (n>>16 | n<<16)& 0xffffffff


以上是关于日常系列LeetCode《6·位运算篇》的主要内容,如果未能解决你的问题,请参考以下文章

日常系列LeetCode《5·数学篇》

日常系列LeetCode《2·一维数组篇》

日常系列LeetCode《9·哈希查找篇》

日常系列LeetCode《10·栈和队列篇》

日常系列LeetCode《4·字符串篇》

日常系列LeetCode《13·综合应用1篇》