@Author:Runsen
@Date:2020/9/11
现在大四基本是重刷数据结构和算法,Runsen不是在刷题的路上,就是在补考化工原理和化工热力学的路上。
其实位运算说简单,也很简单。主要是奇葩很多。
Runsen先总结下基础的内容
& 按位与 将两个操作数对应的每一位进行逻辑与操作,满足:1&1=1,1&0=0,0&1=0,0&0=0,
| 按位或是将两个操作数对应的每一位进行逻辑或操作,满足:1|0=1,0|1=1,1|1=1,0|0=0,
^ 按位异或,将两个操作数对应的每一位进行逻辑异或操作,满足1^1=0,0^0=0,1^0=1,0^1=1
~ 按位取反是将单个操作数对应的每一位取反,~1=0,~0=1
上面的更多的内容查看Runsen之前的博客:七十八、Python | Leetcode位运算系列
下面就是这篇博客重点内容,都是总结极客时间的算法面试通过40讲的位运算的内容。
下面都是Runsen以前不知道,但是现在了解的位运算的操作
交换两数如果不想额外的空间消耗,就可以用位运算来实现,这个是以前不知道的。
>>> x =1
>>> y = 2
>>> x = x^y
>>> y = x^y
>>> x = x^y
>>> x
2
>>> y
1
x & 1 == 1 or == 0
其实是来判断奇数还是偶数,原理就是按照位“与”运算的原则,如果两个值相应的位置都为1也就是上下都为1的时候,那么该结果就是1,如果有一个不是1,那么就是0。因为 & 1
这里固定了,二级制的原因,奇数那么最后一个一定是1,偶数最后一个一定是0。
>>> x = 2 #10
>>> y = 3 #11
>>> x &1
0
>>> y &1
1
因此x & 1 == 1 or == 0
和 x%2 == 0
等价。
这个道理和上面的一样,清除最低位的1就是去掉二级制的最后一个。每执行一次x = x&(x-1),会将x用二进制表示时最右边的一个1变为0,因为x-1将会将该位(x用二进制表示时最右边的一个1)变为0。
>>> x
103
>>> x & (x-1)
102
>>> x = x & (x-1)
>>> x = x & (x-1)
>>> x
100
>>> x = x&(x-1)
>>> x
96
>>> x = x&(x-1)
>>> x
64
x&(-x)
其实就是找到最低是1的位数。
>>> 7 & -7 #111
1
>>> 6 & -6 #110
2
>>> 5 & -5 #101
1
>>> 9 & -9 # 1001
1
>>> 8 & -8
8
>>> 4 & -4
4
>>> 2 & -2
2
>>> 8 % (2**8)
8
>>> 4 % (2**4)
4
>>> 2 % (2**2)
2
当一个偶数与它的负值向与时,结果是能被这个偶数整除的最大的2的n次幂,其实就是 n % (2**n)
当一个奇数与它的负值向与时结果一定是1.
还有很多的复杂的位运算,Runsen是小白的水平,真的看不懂怎么多。
文字版:指定位置
Runsen就试下第一个
>>> 7&(~0<<2) #111
4 #100
>>> 7&(~0<<1)
6 #110
>>> 7&(~0<<0)
7 #111
这个时候,Runsen发现自己好菜啊啊啊啊啊啊啊啊啊啊啊啊啊!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
下面Runsen还是刷下Leetcode上的题,刺激下Runsen弱小的心灵,原来自己是那么的菜菜菜!
最快的方法就是转化为字符串的,然后通过遍历判断的方式或者count的方法求出1的个数。记得需要进行bin操作。
class Solution:
def hammingWeight(self, n: int) -> int:
index = 0
for i in str(bin(n)):
if i == "1":
index = index + 1
return index
但是Runsen学习的是位运算,因此也要学会位运算的方法。方法也很简单:每一次右移一位,判断是 奇数还是偶数,奇数那么就加一,因为位数最后一个肯定是1。
class Solution:
def hammingWeight(self, n: int) -> int:
count = 0
while n != 0:
#x%2 != 0等价
if n&1 != 0:
count = count + 1
n = n >> 1
return count
在Leetcode上,Runsen也看了一个官方的最快的解决的方法。就是遍历数字的 32 位。如果某一位是 1,将计数器加一。其实就是mask左移一位,比如第一次是0001(这里有32位),那么下一次就是0010。
class Solution:
def hammingWeight(self, n: int) -> int:
res = 0
mask = 1
for i in range(32):
if n & mask:
res +=1
mask = mask << 1
return res
若 n = 2 x n = 2^x n=2x,且 x x x 为自然数(即 n n n 为 2 2 2 的幂),则一定满足以下条件:
恒有 n & ( n − 1 ) = = 0 n \& (n - 1) == 0 n&(n−1)==0,这是为什么,Runsen告诉大家,这是因为: 2 x 2^x 2x的位运算只有一个1,那么通过 n & ( n − 1 ) n \& (n - 1) n&(n−1),把那个1拔了出来,然后全部都是000000000了。
不懂的查看Runsen之前的博客:七十四、Python | Leetcode数字系列(下篇)
因此最快的方法就是return n > 0 and n & (n - 1) == 0
,还有一种的方法是通过math.log2最后求出来是不是整数。但是返回的是浮点数,比如是2.0 。所以这种方法就pass。最后一种就是通过不断对2取模运算。最后看看是不是1。
class Solution(object):
def isPowerOfTwo(self, n):
"""
:type n: int
:rtype: bool
"""
if n == 0:
return False
while n % 2 == 0:
n = n / 2
return n == 1
给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。
示例 1:
输入: 2
输出: [0,1,1]
示例 2:
输入: 5
输出: [0,1,1,2,1,2]
最快速的方法就是直接count进行计数就可以了。
class Solution:
def countBits(self, num: int) -> List[int]:
res = []
for i in range(num+1):
count = bin(i).count("1")
res.append(count)
return res
这题还可以使用位运算或者动态规划的方法。Runsen的动态规划真的是烂到家。
思路:动态规划
观察:
十进制0: 二进制0
十进制1: 二进制1
十进制2: 二进制10
十进制3: 二进制11
十进制4: 二进制100
十进制5: 二进制101
二进制中,乘以2相当于左移一位,1的个数不会改变;
由于偶数的二进制形式结尾一定是0,所以一个偶数加1变为奇数,只会将其结尾的0变为1;
所以状态转移方程为:
d p ( i ) = d p ( i / / 2 ) dp(i) = dp(i//2) dp(i)=dp(i//2) 若i为偶数; 这里//2保证是整数,防止溢出
d p ( i ) = d p ( i − 1 ) + 1 dp(i) = dp(i-1)+1 dp(i)=dp(i−1)+1 若i为奇数;
边界条件: dp(0) = 0
class Solution:
def countBits(self, num: int) -> List[int]:
dp = [0] * (num+1)
for i in range(1,num+1):
if i % 2 == 0:
dp[i]=dp[i//2]
else:
dp[i]=dp[i-1]+1
return dp
最后说下位运算:dp[i] = dp[i>>1] + (i&1)
,这里 i>>1代表前一个二进制位的次数, i&1代表i的末尾是否为1,这不就是搞定了吗?这些东西,Runsen前面都是写过的
class Solution:
def countBits(self, num: int) -> List[int]:
dp = [0]
for i in range(1, num + 1):
dp.append(dp[i>>1] + (i&1))
return dp
不知不觉写了很长时间,从下午写到晚上11点,不知道面试笔试的时候,能不能想到用位运算,自己能不能快速反应。