按位操作是真的復雜,且技巧性很高,特此專門開一篇,來簡單講解。
- 目錄:
算法:附錄
算法(1):遞歸
算法(2):鏈表
算法(3):數組
算法(4):字符串
算法(5):二叉樹
算法(6):二叉查找樹
算法(7):隊列和堆棧(附贈BFS和DFS)
算法(8):動態規劃
算法(9):哈希表
算法(10):排序
算法(11):回溯法
算法(12):位操作
1. 原碼
原碼就是符號位加上真值的絕對值, 即用第一位表示符號, 其余位表示值. 比如如果是8位二進制:
[+1]原 = 0000 0001
[-1]原 = 1000 0001
第一位是符號位. 因為第一位是符號位, 所以8位二進制數的取值范圍就是[-127 , 127],即:
[1111 1111 , 0111 1111]
2. 反碼
反碼的表示方法是:
正數的反碼是其本身
負數的反碼是在其原碼的基礎上, 符號位不變,其余各個位取反.
[+1] = [00000001]原 = [00000001]反
[-1] = [10000001]原 = [11111110]反
3. 補碼
補碼的表示方法是:
正數的補碼就是其本身
負數的補碼是在其原碼的基礎上, 符號位不變, 其余各位取反, 最后+1. (即在反碼的基礎上+1)
[+1] = [00000001]原 = [00000001]反 = [00000001]補
[-1] = [10000001]原 = [11111110]反 = [11111111]補
??計算機儲存數據都是用補碼的方式儲存的,c++里面獲取某個數的二進制表示(也就是補碼)如下:cout << bitset<sizeof(int) *8> (num) << endl;
例題1: 計算一個數轉化為2進制之后,包含多少1。( 又稱之為 Hamming weight,漢明重量)
輸入:5
輸出:2(0101)
解釋:
n = n & (n-1)
可以消除二進制位最右邊的一個1。
如:0101 & 0100,結果為 0100, 和 0101相比,第四位的1消失了,計數器加1;
再繼續0100 & 0011,結果為 0000, 和0100相比,第二位的1消失了,計數器加1;
此時n=0,跳出循環。
def solution(n):
count = 0
while n:
n = n & (n-1)
count += 1
return count
ans = solution(15)
print(ans)
例題2:判斷一個數是否為2的指數冪。
輸入:8
輸出:True
解釋:
1的二進制為1
2的二進制為10
4的二進制為100
8的二進制為1000
發現只有最高位為1其余位為0,如果將其減一的話那么最高位為0其余位則為1,兩者相與的結果則必定為0
結論:如果 a&(a-1) == 0 則a必定是2的指數冪
def solution(n):
return (n & (n - 1)) == 0
ans = solution(16)
print(ans)
例題3:判斷一個數是否為4的指數冪。
輸入:16
輸出:True
解釋:
1的二進制為1
4的二進制為100
16的二進制為10000
第一句不多說,(num & (num - 1)) == 0
用來判斷是不是2的指數冪,第二句則是去除掉是2的指數冪,但不是4的指數冪的部分。
def solution(num):
return (num & (num - 1)) == 0 and (num - 1) % 3 == 0
#或者下面這種寫法也行
# return (n&(n-1)) == 0 and n & 0x55555555
ans = solution(16)
print(ans)
例題4:兩整數求和。
輸入:-1,1
輸出:0
解釋:
下面代碼中 a 儲存不包含進位的計算結果,即(a ^ b)
;
b 只儲存進位的結果,即(a & b) << 1)
;
當b等于0時,代表沒有進位,跳出循環。
至于mask的作用,因為理論上 python 里 int 類型的位數是沒有上限的,不同與c++,int為32位。而最后一句的~(a ^ mask)
,其實在二進制位上,執行前后是的結果是一樣的,但表達含義變化了,將第一位變為了符號位。
如 4294967276(大于2的31次方),其二進制表示為:
...11111111111111111111111111101100(...表示前面還有很多位,因為 int 類型的位數是沒有上限),
a ^ mask
得:
00000000000000000000000000010011(此時前面的都mask掉了,第一位為符號位,該數為19)
再^
求反得:
11111111111111111111111111101100(和4294967276的二進制完全相同,但是含義變成了-20)
class Solution:
def getSum(self, a, b):
"""
:type a: int
:type b: int
:rtype: int
"""
# 32 bits integer max
MAX = 0x7FFFFFFF
# 32 bits interger min
MIN = 0x80000000
# mask to get last 32 bits
mask = 0xFFFFFFFF
while b != 0:
# ^ get different bits and & gets double 1s, << moves carry
a, b = (a ^ b) & mask, ((a & b) << 1) & mask
# if a is negative, get a's 32 bits complement positive first
# then get 32-bit positive's Python complement negative
return a if a <= MAX else ~(a ^ mask)
例題5:缺失的數字
給一個長度為n的數組,里面存放從0到n的數,但是少了一個,求出該數。
輸入:[0,1,2,4,5]
輸出:3
解釋:對0和某一個數進行異或操作,則結果為該數;
某個數對另一個數進行兩次 ^
異或操作,則結果還是該數本身。
def solution(arr):
ans = 0
for i,num in enumerate(arr):
ans ^= num
ans ^= i
return ans^len(arr)
ans = solution([0,1,2,4,5])
print(ans)
例題6:將一個32位無符號整數,進行按位翻轉
輸入:12
輸出:805306368
解釋:
00000000000000000000000000001100 (12的32進制表示)
00110000000000000000000000000000 (805306368的32進制表示)
def solution(n):
ans = 0
mask = 1<<31
while n:
if n&1:
ans |= mask
mask >>= 1
n>>= 1
return ans
n = 12
print(bin(n)[2:].zfill(32))
ans = solution(n)
print(bin(ans)[2:].zfill(32))
例題7:逐個元素進行and 操作。給出一個區間[m, n],且 0 <= m <= n <= 2147483647,對m到n之間的每個元素進行與操作,返回操作的結果。
輸入:[5,7]
輸出:4 (101 &110 & 111)
解釋:將5和7一直左移,并不斷判斷左移后兩個數是否相等,如果相等(此時已經左移兩次,1==1),則跳出循環,然后右移相同位數(兩位),并返回結果,即4。
def solution(m,n):
a = 0
while m!=n:
m>>=1
n>>=1
a += 1
return m<<a
ans = solution(5,7)
print(ans)
多運算符混合應用題目
例題8: DNA重復序列。某DNA序列由ACGT四個字母組成,需要找出里面所有長度為10,且出現重復的序列。
輸入:'AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT'
輸出:['AAAAACCCCC', 'CCCCCAAAAA']
輸入:’AAAAAAAAAAA'
輸出:['AAAAAAAAAA']
附注:注意里面的運算優先級,首先 +/-優先級最大,其次<< , 隨后是 &,| 的優先級最小。
def solution(s):
d = {}
ans = []
num = 0
if len(s)<11: return []
for i in range(9):
num = num<<3 | ord(s[i]) - ord('A')+1 & 7
# 上面相當于 num = (num<<3) | ((ord(s[i]) - ord('A')+1) & 7)
for i in range(9,len(s)):
num = num << 3 & 0x3fffffff | (ord(s[i]) - ord('A')+1) & 7
if d.get(num) == 1:
ans.append(s[i-9:i+1])
d[num]=d.get(num,0)+1
return ans
ans = solution('AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT')
print(ans)
例題9:單獨數字。給出一個數組,該數組中有兩個元素只出現一次,其他的都出現兩次,請找出這兩個元素。
輸入:[1,1,2,2,3,4]
輸出:[3,4]
解釋:第一步,對所有元素進行異或操作,某一位出現1的次數為奇數次,才會為1;
第二步,bit = bit & ~(bit - 1)
,找出最右邊第一位為1的二進制位,其余全置零。
(eg:bit = 101100,執行完之后,bit = 000100);
第三步,再次遍歷數組,通過該比特位將數據分為兩部分,這兩部分各包含一個只出現一次的元素,以及若干個成對出現的元素。
def solution(arr):
bit = 0
for i in arr:
bit ^=i
bit = bit & ~(bit - 1)
ans =[0,0]
for i in arr:
if bit & i:
ans[0]^=i
else:
ans[1]^=i
return ans
ans = solution([1,2,3,5,1,3])
print(ans)
例題10:單獨數字。給一個數組,其中只有一個元素出現一次,其他都出現了三次,找出該元素。
輸入:[1,1,1,3,4,4,4]
輸出:3
解釋:(此題解法通用性很強,題目可以改成:一個元素出現m次,其他元素都出現了k次,只要m不為k的整數倍即可用該題方法來求解。)
def solution(arr):
barr = [0] * 32
for n in arr:
for i in range(0, 32):
if n & (1 << i):
barr[i] = (barr[i] + 1) % 3
ans = 0
for i in range(0, 32):
ans = ans | barr[i] << i
return ans if ans <= 0x7fffffff else ~(ans ^ 0xffffffff)
ans = solution([1,1,1,-3])
print(ans)
例題11:單詞長度的乘積。給定一個包含各種單詞的數組,返回一個最大值,該最大值為:兩個不含相同字符的單詞,其長度的乘積最大值(即len(word[i]) * len(word[j])
) 。
輸入:["a","ab","abc","d","cd","bcd","abcd"]
輸出:4 ( len('ab') * len('cd') = 4)
輸入: ["a","aa","aaa","aaaa"]
輸出: 0 (找不到滿足條件的一對單詞)
def maxProduct(words) :
mask = [0] * len(words)
ans = 0
for i in range(len(words)):
for c in words[i]:
mask[i] |= 1 << ord(c) - ord('a')
for j in range(i):
if not mask[i] & mask[j]:
ans = max(ans, len(words[i]) * len(words[j]))
return ans
ans = maxProduct(["abcw","baz","foo","bar","xtfn","abcdef"])
print(ans)
例題12: 求一個集合所有子集。(這應該是最后一題了,,,)
輸入:[2,3,5]
輸出:[[], [2], [3], [2, 3], [5], [2, 5], [3, 5], [2, 3, 5]]
解釋:
對于集合當中每個數字,都有要、不要兩種選擇,所以子集個數為 2的len(arr)次方 (即下面的 size)。
注意下面的關鍵句if (1 << j & i)
,該句子可以實現如下取舍方式:
對于第一個數,1 << j = 1
, 二進制為0001,與 i 進行按位與操作后,其取舍方式為:
0,1,0,1,0,1,0,1...
對于第二個數,1 << j = 2
, 二進制為0010,與 i 進行按位與操作后,其取舍方式為:
0,0,1,1,0,0,1,1...
對于第三個數,1 << j = 4
, 二進制為0100,與 i 進行按位與操作后,其取舍方式為:
0,0,0,0,1,1,1,1...
def solution(arr):
size = 1 << len(arr)
print(size)
ans = [[] for i in range(size)]
for i in range(len(ans)):
for j in range(len(arr)):
if (1 << j & i):
ans[i].append(arr[j])
return ans
ans = solution([2,3,5])
print(ans)
暫且完結,撒花~~~