1. 六十一、深入學(xué)習(xí)位運(yùn)算

        共 2610字,需瀏覽 6分鐘

         ·

        2020-12-20 07:23


        「@Author:Runsen」

        ?

        編程的本質(zhì)來源于算法,而算法的本質(zhì)來源于數(shù)學(xué),編程只不過將數(shù)學(xué)題進(jìn)行代碼化。「---- Runsen」

        ?

        總結(jié)下之前基礎(chǔ)的內(nèi)容

        • & 按位與 將兩個(gè)操作數(shù)對應(yīng)的每一位進(jìn)行邏輯與操作,滿足:1&1=1,1&0=0,0&1=0,0&0=0,

        • | 按位或是將兩個(gè)操作數(shù)對應(yīng)的每一位進(jìn)行邏輯或操作,滿足:1|0=1,0|1=1,1|1=1,0|0=0,

        • ^ 按位異或,將兩個(gè)操作數(shù)對應(yīng)的每一位進(jìn)行邏輯異或操作,滿足1^1=0,0^0=0,1^0=1,0^1=1

        • ~ 按位取反是將單個(gè)操作數(shù)對應(yīng)的每一位取反,~1=0,~0=1

        下面就是這篇博客重點(diǎn)內(nèi)容,總結(jié)于極客時(shí)間的算法面試通過40講的位運(yùn)算的內(nèi)容。

        下面就是學(xué)習(xí)記錄的筆記

        交換兩數(shù)

        交換兩數(shù)如果不想額外的空間消耗,就可以用位運(yùn)算來實(shí)現(xiàn),這個(gè)是以前不知道的。

        >>>?x?=1
        >>>?y?=?2
        >>>?x?=?x^y
        >>>?y?=?x^y
        >>>?x?=?x^y
        >>>?x
        2
        >>>?y
        1

        判斷奇數(shù)和偶數(shù)

        x & 1 == 1 or == 0其實(shí)是來判斷奇數(shù)還是偶數(shù),原理就是按照位“與”運(yùn)算的原則,如果兩個(gè)值相應(yīng)的位置都為1也就是上下都為1的時(shí)候,那么該結(jié)果就是1,如果有一個(gè)不是1,那么就是0。

        因?yàn)?& 1這里固定了,二級(jí)制的原因,奇數(shù)那么最后一個(gè)一定是1,偶數(shù)最后一個(gè)一定是0。

        >>>?x?=?2?#10?
        >>>?y?=?3?#11
        >>>?x?&?1
        0
        >>>?y?&?1
        1

        因此使用x & 1 == 1 or == 0判斷奇數(shù)或者偶數(shù)和 x%2 == 0等價(jià)。

        清除最低位的1

        這個(gè)道理和上面的一樣,清除最低位的1就是去掉二級(jí)制的最后一個(gè)。每執(zhí)行一次x = x&(x-1),會(huì)將x用二進(jìn)制表示時(shí)最右邊的一個(gè)1變?yōu)?,因?yàn)閤-1將會(huì)將該位(x用二進(jìn)制表示時(shí)最右邊的一個(gè)1)變?yōu)?。

        >>>?x?=?7
        >>>?bin(x)
        '0b111'
        >>>?x?&?(x-1)
        6
        >>>?bin(6)
        '0b110'
        >>>?x?=?x?&?(x-1)
        >>>?x?&?(x-1)
        4
        >>>?bin(4)
        '0b100'

        x&(-x)

        x&(-x)的意思

        • 當(dāng)x是一個(gè)偶數(shù)與它的負(fù)值向與時(shí),結(jié)果是能被這個(gè)偶數(shù)整除的最大的2的n次冪,x&(-x)返回x與2^64的最大公約數(shù),即x最多能被n個(gè)2整除就返回2^n。

        • 當(dāng)x是一個(gè)奇數(shù)與它的負(fù)值向與時(shí)結(jié)果一定是1,則返回得到最低的1

        >>>?7?&?-7?#111??7二進(jìn)制最低是1的位數(shù)是1
        1
        >>>?6?&?-6?#??6與2^64最大公約數(shù)是2
        2
        >>>?4?&?-4?#?4與2^64最大公約數(shù)是4
        4
        >>>?8&?-8??#?8與2^64最大公約數(shù)是8
        8
        >>>?9?&?-9?#?1001
        1

        文字版:指定位置

        • 將 x 最右邊的 n 位清零:x &(~0<
        • 獲取 x 的第 n 位值(0或者1):(x>>n)&1
        • 獲取x的第n位的冪值:x&(1<
        • 僅將第 n 位置為1:x|(1<
        • 僅將第 n 位置為0:x&(~(1<
        • 將 x 最高位至第 n 位(含)清零:x&((1<
        • 將 第n位 至第 0 位(含)清零:x&(~((1<<(n+1))-1))

        還有很多的復(fù)雜的位運(yùn)算,Runsen是小白的水平,真的看不懂怎么多。

        下面是嘗試使用

        >>>?7&(~0<<2)?#111??將最右邊的2位清零
        4?#100
        >>>?7&(~0<<1)??#111??將最右邊的1位清零
        6?#110
        >>>?(2>>5)&1??#101??獲取?5?的第?2?位值
        0

        Leetcode 191 :統(tǒng)計(jì)位1的個(gè)數(shù)

        編寫一個(gè)函數(shù),輸入是一個(gè)無符號(hào)整數(shù)(以二進(jìn)制串的形式),返回其二進(jìn)制表達(dá)式中數(shù)字位數(shù)為 '1' 的個(gè)數(shù)(也被稱為漢明重量)。

        輸入:00000000000000000000000000001011
        輸出:3
        解釋:輸入的二進(jìn)制串?00000000000000000000000000001011 中,共有三位為?'1'。

        最快的方法就是轉(zhuǎn)化為字符串的,然后通過遍歷判斷的方式或者count的方法求出1的個(gè)數(shù)。記得需要進(jìn)行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學(xué)習(xí)的是位運(yùn)算,因此也要學(xué)會(huì)位運(yùn)算的方法。方法也很簡單:每一次右移一位,判斷是 奇數(shù)還是偶數(shù),奇數(shù)那么就加一,因?yàn)槲粩?shù)最后一個(gè)肯定是1。

        class?Solution:
        ????def?hammingWeight(self,?n:?int)?->?int:
        ????????count?=?0
        ????????while?n?!=?0:
        ????????????#x%2?!=?0等價(jià)
        ????????????if?n&1?!=?0:
        ????????????????count?=?count?+?1
        ????????????n?=?n?>>?1?
        ????????return?count

        在Leetcode上,Runsen也看了一個(gè)官方的最快的解決的方法。就是遍歷數(shù)字的 32 位。如果某一位是 1,將計(jì)數(shù)器加一。其實(shí)就是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??

        Leetcode231:2的冪次方問題

        給定一個(gè)整數(shù),編寫一個(gè)函數(shù)來判斷它是否是 2 的冪次方。

        輸入:?1
        輸出:?true
        解釋:?20?=?1

        輸入:?16
        輸出:?true
        解釋:?24?=?16

        ,且 為自然數(shù)(即 的冪),則一定滿足以下條件:恒有 ,這是為什么,Runsen告訴大家,這是因?yàn)椋?span style="cursor:pointer;">的位運(yùn)算只有一個(gè)1,那么通過 ,把那個(gè)1拔了出來,然后全部都是000000000了。

        因此最快的方法就是return n > 0 and n & (n - 1) == 0,還有一種的方法是通過math.log2最后求出來是不是整數(shù)。但是返回的是浮點(diǎn)數(shù),比如是2.0 。所以這種方法就pass。最后一種就是通過不斷對2取模運(yùn)算。最后看看是不是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

        Leetcode338:比特位計(jì)數(shù)問題

        給定一個(gè)非負(fù)整數(shù) num。對于 0 ≤ i ≤ num 范圍中的每個(gè)數(shù)字 i ,計(jì)算其二進(jìn)制數(shù)中的 1 的數(shù)目并將它們作為數(shù)組返回。

        示例?1:

        輸入:?2
        輸出:?[0,1,1]
        示例?2:

        輸入:?5
        輸出:?[0,1,1,2,1,2]

        最快速的方法就是直接count進(jìn)行計(jì)數(shù)就可以了。

        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

        這題還可以使用位運(yùn)算或者動(dòng)態(tài)規(guī)劃的方法,Runsen的動(dòng)態(tài)規(guī)劃真的是爛到家。

        思路:動(dòng)態(tài)規(guī)劃

        觀察:
        十進(jìn)制0:?二進(jìn)制0
        十進(jìn)制1:?二進(jìn)制1
        十進(jìn)制2:?二進(jìn)制10
        十進(jìn)制3:?二進(jìn)制11
        十進(jìn)制4:?二進(jìn)制100
        十進(jìn)制5:?二進(jìn)制101

        二進(jìn)制中,乘以2相當(dāng)于左移一位,1的個(gè)數(shù)不會(huì)改變;由于偶數(shù)的二進(jìn)制形式結(jié)尾一定是0,所以一個(gè)偶數(shù)加1變?yōu)槠鏀?shù),只會(huì)將其結(jié)尾的0變?yōu)?;

        所以狀態(tài)轉(zhuǎn)移方程為:

        $dp(i) = dp(i//2)$ 若i為偶數(shù);這里//2保證是整數(shù),防止溢出

        $dp(i) = dp(i-1)+1$ 若i為奇數(shù);

        邊界條件: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

        最后說下位運(yùn)算:dp[i] = dp[i>>1] + (i&1),這里 ?i>>1代表前一個(gè)二進(jìn)制位的次數(shù), ?i&1代表i的末尾是否為1,因此可以繼續(xù)將代碼變得更簡潔。

        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
        ?

        本文已收錄 GitHub,傳送門~[1] ,里面更有大廠面試完整考點(diǎn),歡迎 Star。

        ?


        Reference

        [1]

        傳送門~: https://github.com/MaoliRUNsen/runsenlearnpy100

        更多的文章

        點(diǎn)擊下面小程序



        - END -

        瀏覽 51
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 影音先锋国产在线 | 亚洲色午夜 | 国产丝袜啪啪 | 国产羞羞羞视频在线观看 | 极品少妇久久久 |