今日獲得: 位運算真的是 666, 計算機基礎還有數學知識都很重要.html
LeetCode上第 191 號問題:編寫一個函數,輸入是一個無符號整數,返回其二進制表達式中數字位數爲 ‘1’ 的個數。python
觀察一下 n 與 n-1 這兩個數的二進制表示:對於 n-1 這個數的二進制來講,相對於 n 的二進制,它的最末位的一個 1 會變成 0,最末位一個 1 以後的 0 會所有變成 1,其它位相同不變。面試
好比 n = 8888,其二進制爲 10001010111000算法
則 n - 1 = 8887 ,其二進制爲 10001010110111數組
經過按位與操做後:n & (n-1) = 10001010110000ide
也就是說:經過 n&(n-1)這個操做,能夠起到消除最後一個1的做用。函數
因此能夠經過執行 n&(n-1) 操做來消除 n 末尾的 1 ,消除了多少次,就說明有多少個 1 。ui
class Solution: def hammingWeight(self, n): res = 0 while n != 0: res += 1 n &= (n - 1) return res def hammingWeight2(self, n): res = 0 while n != 0: res += (n & 1) n = n >> 1 return res
給定一個整數,編寫一個函數來判斷它是不是 2 的冪次方。3d
仔細觀察,能夠看出 2 的次方數都只有一個 1 ,剩下的都是 0 。根據這個特色,只須要每次判斷最低位是否爲 1 ,而後向右移位,最後統計 1 的個數便可判斷是不是 2 的次方數, 可使用上一個問題的解法
def isPowerOfTwo(n): res = 0 while n != 0: res += (n & 1) n >>= 1 return res == 1
該題還有一種巧妙的解法。再觀察上面的表格,若是一個數是 2 的次方數的話,那麼它的二進數必然是最高位爲1,其它都爲 0 ,那麼若是此時咱們減 1 的話,則最高位會降一位,其他爲 0 的位如今都爲變爲 1,那麼咱們把兩數相與,就會獲得 0.
好比 2 的 3 次方爲 8,二進制位 1000 ,那麼 8 - 1 = 7
,其中 7 的二進制位 0111
class Solution: def isPowerOfTwo(self, n: int) -> bool: return (n > 0) and ((n & (n - 1)) == 0)
給定範圍 [m, n],其中 0 <= m <= n <= 2147483647,返回此範圍內全部數字的按位與(包含 m, n 兩端點)
全部這些位字符串的公共前綴也是指定範圍的起始和結束編號的公共前綴(即在上面的示例中分別爲 9 和 12),所以,咱們能夠將問題從新表述爲:給定兩個整數,要求咱們找到她們二進制字符串的公共前綴
Brian Kernighan 算法
n & (n-1)
class Solution: def rangeBitwiseAnd(self, m: int, n: int) -> int: while m < n: # turn off rightmost 1-bit n = n & (n - 1) return m & n
全部 DNA 都由一系列縮寫爲 A,C,G 和 T 的核苷酸組成,例如:「ACGAATTCCG」。在研究 DNA 時,識別 DNA 中的重複序列有時會對研究很是有幫助。
編寫一個函數來查找目標子串,目標子串的長度爲 10,且在 DNA 字符串 s 中出現次數超過一次。差點沒看懂題 QAQ!
輸入:s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT" 輸出:["AAAAACCCCC", "CCCCCAAAAA"]
# 普通解法 class Solution: def findRepeatedDnaSequences(self, s: str) -> List[str]: d = {} for i in range(len(s) - 9): k = s[i: i+10] if k in d: d[k] = True else: d[k] = False return [*filter(lambda x: d[x], d)]
該的位運算解法暫時沒看懂,先記錄着,有點暈了,後面繼續看
給定一個非空整數數組,除了某個元素只出現一次之外,其他每一個元素均出現兩次。找出那個只出現了一次的元素。
要求: 你的算法應該具備線性時間複雜度。 你能夠不使用額外空間來實現嗎?
輸入: [2,2,1] 輸出: 1 輸入: [4,1,2,1,2] 輸出: 4
這題比較簡單,想到異或運算,相同爲0,不一樣爲1的規則就能夠很快求解了
a | b | a⊕b |
---|---|---|
1 | 0 | 1 |
1 | 1 | 0 |
0 | 0 | 0 |
0 | 1 | 1 |
class Solution: def singleNumber(self, nums: List[int]) -> int: # 非空數組暫時不用判斷 from functools import reduce return reduce(lambda a, b: a ^ b, nums)
給定一個非空整數數組,除了某個元素只出現一次之外,其他每一個元素均出現了三次。找出那個只出現了一次的元素。
要求: 你的算法應該具備線性時間複雜度。 你能夠不使用額外空間來實現嗎?
輸入: [2,2,3,2] 輸出: 3 輸入: [0,1,0,1,0,1,99] 輸出: 99
3×(a+b+c)−(a+a+a+b+b+b+c)=2c 也能夠應用在上一題
## 普通解法 class Solution: def singleNumber(self, nums): return (3 * sum(set(nums)) - sum(nums)) // 2
推廣到通常狀況:
若是其餘數都出現了 k 次,一個數出現了一次。那麼若是 k 是偶數,仍是把全部的數異或起來就好了。若是 k 是奇數,那麼統計每一位是 1 的個數,而後模 k 取餘數就能獲得那個單獨的數了 。其中有
sum = kn + 1
位運算的解法是有限狀態機+位運算,感受有點難理解,本身推敲一遍勉強能夠理解,本身畫一個狀態表,而後推導出響應的公式就比較好了。
我是先看題解1, 在看題解2,才搞明白了。
幾乎每道題都能看到題解2的做者,佩服不已,時而習之,但求甚解。
class Solution: def singleNumber(self, nums: List[int]) -> int: ones, twos = 0, 0 for num in nums: ones = ones ^ num & ~twos twos = twos ^ num & ~ones return ones
給定一個整數數組
nums
,其中剛好有兩個元素只出現一次,其他全部元素均出現兩次。 找出只出現一次的那兩個元素。輸入: [1,2,1,3,2,5] 輸出: [3,5]
注意:
[5, 3]
也是正確答案。根據前面找一個不一樣數的思路算法,在這裏把全部元素都異或,那麼獲得的結果就是那兩個只出現一次的元素異或的結果。
若是咱們把原數組分紅兩組,只出現過一次的兩個數字分別在兩組裏邊,那麼問題就轉換成以前的老問題了,只須要這兩組裏的數字各自異或,答案就出來了。
那麼經過什麼把數組分紅兩組呢?
放眼到二進制,咱們要找的這兩個數字是不一樣的,因此它倆至少有一位是不一樣的,因此咱們能夠根據這一位,把數組分紅這一位都是 1 的一類和這一位都是 0 的一類,這樣就把這兩個數分到兩組裏了。
那麼怎麼知道那兩個數字哪一位不一樣呢?
回到咱們異或的結果,若是把數組中的全部數字異或,最後異或的結果,其實就是咱們要找的兩個數字的異或。而異或結果若是某一位是 1,也就意味着當前位兩個數字一個是 1 ,一個是 0,也就找到了不一樣的一位。
以上思路源於做者:windliang
class Solution: def FindNumsAppearOnce(self, nums): length = len(nums) if length <= 0: return nums result = 0 # 先將全部數子異或獲得一個值 for num in nums: result ^= num # 找到這個值最低位二進制位1的位置,根據這個位置來區分兩個數組,分別異或求出只出現一次的數字 firstBitIndex = self.FindFirstBit(result) n1, n2 = 0, 0 for num in nums: if self.IsSameBit(num, firstBitIndex): n1 ^= num else: n2 ^= num return n1, n2 def FindFirstBit(self, num): indexBit = 0 while num & 1 == 0: indexBit += 1 num = num >> 1 return indexBit def IsSameBit(self, num, indexBit): num = num >> indexBit return num & 1
# 解放2 class Solution2: def FindNumsAppearOnce(self, nums): length = len(nums) if length <= 0: return [] diff = 0 for i in nums: diff ^= i n1, n2 = 0, 0 minDiff = self.getMinDiff(diff) for num in nums: if minDiff & num == 0: n1 ^= num n2 = diff ^ n1 return n1, n2 def getMinDiff(self, num): # 保留一個低位是1的數字 # 取負號其實就是先取反,再加 1,須要 補碼 的知識。最後再和原數相與就會保留最低位的 1。好比 1010,先取反是 0101,再加 1,就是 0110,再和 1010 相與,就是 0010 了 return num & (-num)
diff &= -diff ; 這裏 的作法。
取負號其實就是先取反,再加 1,須要 補碼 的知識。最後再和原數相與就會保留最低位的 1。好比 1010
,先取反是 0101
,再加 1,就是 0110
,再和 1010
相與,就是 0010
了。
diff = (diff & (diff - 1)) ^ diff; 這裏 的作法
n & (n - 1)
的操做在 191 題 用過,它能夠將最低位的 1 置爲 0。好比 1110
,先將最低位的 1 置爲 0 就變成 1100
,而後再和原數 1110
異或,就獲得了 0010
diff = xor & ~(diff - 1) 這裏 的作法
先減 1
,再取反,再相與。好比 1010
減 1
就是 1001
,而後取反 0110
,而後和原數 1010
相與,就是 0010
了
mask=1 while((diff & mask)==0): mask <<= 1 # mask 就是咱們要構造的了
這裏 的作法
補碼爲何按位取反再加一認真看完會有收穫的。