【算法技巧】位運算裝逼指南

位算法的效率有多快我就不說,不信你能夠去用 10 億個數據模擬一下,今天給你們講一講位運算的一些經典例子。不過,最重要的不是看懂了這些例子就好,而是要在之後多去運用位運算這些技巧,固然,採用位運算,也是能夠裝逼的,不信,你往下看。我會從最簡單的講起,一道比一道難度遞增,不過竟然是講技巧,那麼也不會太難,相信你分分鐘看懂。面試

判斷奇偶數

判斷一個數是基於仍是偶數,相信不少人都作過,通常的作法的代碼以下算法

if( n % 2) == 01
    // n 是個奇數
}
複製代碼

若是把 n 以二進制的形式展現的話,其實咱們只須要判斷最後一個二進制位是 1 仍是 0 就好了,若是是 1 的話,表明是奇數,若是是 0 則表明是偶數,因此採用位運算的方式的話,代碼以下:bash

if(n & 1 == 1){
    // n 是個奇數。
}
複製代碼

有人可能會說,咱們寫成 n % 2 的形式,編譯器也會自動幫咱們優化成位運算啊,這個確實,有些編譯器確實會自動幫咱們優化。可是,咱們本身可以採用位運算的形式寫出來,固然更好了。別人看到你的代碼,我靠,牛逼啊。無形中還能裝下逼,是否是。固然,時間效率也快不少,不信你去測試測試。函數

二、交換兩個數

交換兩個數相信不少人每天寫過,我也相信你每次都會使用一個額外來變量來輔助交換,例如,咱們要交換 x 與 y 值,傳統代碼以下:工具

int tmp = x;
x = y;
y = tmp;
複製代碼

這樣寫有問題嗎?沒問題,通俗易懂,萬一哪天有人要爲難你,**不容許你使用額外的輔助變量來完成交換呢?**你還別說,有人面試確實被問過,這個時候,位運算大法就來了。代碼以下:學習

x = x ^ y   // (1)
y = x ^ y   // (2)
x = x ^ y   // (3)
複製代碼

我靠,牛逼!三個都是 x ^ y,就莫名交換成功了。在此我解釋下吧,咱們知道,兩個相同的數異或以後結果會等於 0,即 n ^ n = 0。而且任何數與 0 異或等於它自己,即 n ^ 0 = n。因此,解釋以下:開發工具

把(1)中的 x 帶入 (2)中的 x,有測試

y = x^y = (x^y)^y = x^(y^y) = x^0 = x。 x 的值成功賦給了 y。優化

對於(3),推導以下:ui

x = x^y = (x^y)^x = (x^x)^y = 0^y = y。

這裏解釋一下,異或運算支持運算的交換律和結合律哦。

之後你要是別人看不懂你的代碼,逼格裝高點,就能夠在代碼裏面採用這樣的公式來交換兩個變量的值了,被打了不要找我。

講這個呢,是想告訴你位運算的強大,讓你之後可以更多着去利用位運算去解決一些問題,一時之間學不會也沒事,看多了就學會了,不信?繼續往下看,下面的這幾道題,也是很是常見的,可能你以前也都作過。

三、找出沒有重複的數

給你一組整型數據,這些數據中,其中有一個數只出現了一次,其餘的數都出現了兩次,讓你來找出一個數 。

這道題可能不少人會用一個哈希表來存儲,每次存儲的時候,記錄 某個數出現的次數,最後再遍歷哈希表,看看哪一個數只出現了一次。這種方法的時間複雜度爲 O(n),空間複雜度也爲 O(n)了。

然而我想告訴你的是,採用位運算來作,絕對高逼格!

咱們剛纔說過,兩個相同的數異或的結果是 0,一個數和 0 異或的結果是它自己,因此咱們把這一組整型所有異或一下,例如這組數據是:1, 2, 3, 4, 5, 1, 2, 3, 4。其中 5 只出現了一次,其餘都出現了兩次,把他們所有異或一下,結果以下:

因爲異或支持交換律和結合律,因此:

1^2^3^4^5^1^2^3^4 = (1^1)^(2^2)^(3^3)^(4^4)^5= 0^0^0^0^5 = 5。

也就是說,那些出現了兩次的數異或以後會變成0,那個出現一次的數,和 0 異或以後就等於它自己。就問這個解法牛不牛逼?因此代碼以下

int find(int[] arr){
    int tmp = arr[0];
    for(int i = 1;i < arr.length; i++){
        tmp = tmp ^ arr[i];
    }
    return tmp;
}
複製代碼

時間複雜度爲 O(n),空間複雜度爲 O(1),並且看起來很牛逼。

四、3的n次方

若是讓你求解 3 的 n 次方,而且不能使用系統自帶的 pow 函數,你會怎麼作呢?這還不簡單,連續讓 n 個 3 相乘就好了,代碼以下:

int pow(int n){
    int tmp = 1;
    for(int i = 1; i <= n; i++) {
        tmp = tmp * 3;
    }
    return tmp;
}
複製代碼

不過你要是這樣作的話,我只能呵呵,時間複雜度爲 O(n) 了,怕是小學生都會!若是讓你用位運算來作,你會怎麼作呢?

我舉個例子吧,例如 n = 13,則 n 的二進制表示爲 1101, 那麼 3 的 13 次方能夠拆解爲:

3^1101 = 3^0001 * 3^0100 * 3^1000。

咱們能夠經過 & 1和 >>1 來逐位讀取 1101,爲1時將該位表明的乘數累乘到最終結果。直接看代碼吧,反而容易理解:

int pow(int n){
    int sum = 1;
    int tmp = 3;
    while(n != 0){
        if(n & 1 == 1){
            sum *= tmp;
        }
        tmp *= tmp;
        n = n >> 1;
    }
    
    return sum;
}
複製代碼

時間複雜度近爲 O(logn),並且看起來很牛逼。

這裏說一下,位運算不少狀況下都是很二進制扯上關係的,因此咱們要判斷是不是否位運算,不少狀況下都會把他們拆分紅二進制,而後觀察特性,或者就是利用與,或,異或的特性來觀察,總之,我以爲多看一些例子,加上本身多動手,就比較容易上手了。因此呢,繼續往下看,注意,先別看答案,先看看本身會不會作。

五、找出不大於N的最大的2的冪指數

傳統的作法就是讓 1 不斷着乘以 2,代碼以下:

int findN(int N){
    int sum = 1;
   while(true){
        if(sum * 2 > N){
            return sum;
        }
        sum = sum * 2;
   }
}
複製代碼

這樣作的話,時間複雜度是 O(logn),那若是改爲位運算,該怎麼作呢?我剛纔說了,若是要弄成位運算的方式,不少時候咱們把某個數拆成二進制,而後看看有哪些發現。這裏我舉個例子吧。

例如 N = 19,那麼轉換成二進制就是 00010011(這裏爲了方便,我採用8位的二進制來表示)。那麼咱們要找的數就是,把二進制中最左邊的 1 保留,後面的 1 所有變爲 0。即咱們的目標數是 00010000。那麼如何得到這個數呢?相應解法以下:

一、找到最左邊的 1,而後把它右邊的全部 0 變成 1

二、把獲得的數值加 1,能夠獲得 00100000即 00011111 + 1 = 00100000。

三、把 獲得的 00100000 向右移動一位,便可獲得 00010000,即 00100000 >> 1 = 00010000。

那麼問題來了,第一步中把最左邊 1 中後面的 0 轉化爲 1 該怎麼弄呢?我先給出代碼再解釋吧。下面這段代碼就能夠把最左邊 1 中後面的 0 所有轉化爲 1,

n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
複製代碼

就是經過把 n 右移而且作運算便可獲得。我解釋下吧,咱們假設最左邊的 1 處於二進制位中的第 k 位(從左往右數),那麼把 n 右移一位以後,那麼獲得的結果中第 k+1 位也一定爲 1,而後把 n 與右移後的結果作或運算,那麼獲得的結果中第 k 和 第 k + 1 位一定是 1;一樣的道理,再次把 n 右移兩位,那麼獲得的結果中第 k+2和第 k+3 位一定是 1,而後再次作或運算,那麼就能獲得第 k, k+1, k+2, k+3 都是 1,如此往復下去....

最終的代碼以下

int findN(int n){
    n |= n >> 1;
    n |= n >> 2;
    n |= n >> 4;
    n |= n >> 8 // 整型通常是 32 位,上面我是假設 8 位。
    return (n + 1) >> 1;
}
複製代碼

這種作法的時間複雜度近似 O(1),重點是,高逼格。

總結

上面講了 5 道題,原本想寫十道的,發現五道就已經寫了很久了,,,,十道的話,怕大家也沒耐心寫完,並且一道比一道難的那種,,,,。

不過呢,我給出的這些例子中,並非讓大家學會了這些題就 Ok,並且讓大家有一個意識:不少時候,位運算是個不錯的選擇,至少時間效率會快不少,並且高逼格,裝逼必備。因此呢,之後能夠多嘗試去使用位運算哦,之後我會再給你們找些題來說講,遇到高逼格的,感受很不錯的,就會拿來供你們學習了。

若是你以爲該文章不錯,不妨

一、點贊,讓更多的人也能看到這篇內容(收藏不點贊,都是耍流氓 -_-)

二、關注我,讓咱們成爲長期關係

三、關注公衆號「苦逼的碼農」,裏面已有100多篇原創文章,我也分享了不少視頻、書籍的資源,以及開發工具,歡迎各位的關注,第一時間閱讀個人文章。

相關文章
相關標籤/搜索