位運算就能做到,就不要寫那麼多代碼了【位運算奇淫技巧】

2021-03-02 日拱一兵
前言

位運算隱藏在程式語言的角落中,其神秘而又強大,暗藏內力,有些人光聽位運算的大名的心中忐忑,還有些人更是一看到位運算就遠遠離去,我之前也是。但狡猾的面試官往往喜歡搞偷襲,抓住我們的弱點搞我們,為了防患於未然,特記此篇!

本篇的內容為位運算的介紹和一些比較經典的位運算問題進行介紹分析,當然,位運算這麼牛,後面肯定還是要歸納總結的。

認識位運算

什麼是位運算?

程序中的所有數在計算機內存中都是以二進位的形式儲存的。位運算就是直接對整數在內存中的二進位位進行操作。

位運算就是直接操作二進位數,那麼有哪些種類的位運算呢?

常見的運算符有與(&)、或(|)、異或(^)、取反(~)、左移(<<)、右移(>>是帶符號右移 >>>無符號右移動)。下面來細看看每一種位運算的規則。

位運算 & (與)

規則:二進位對應位兩兩進行邏輯AND運算(只有對應位的值都是 1 時結果才為 1, 否則即為 0)即 0&0=0,0&1=0,1&1=1

例如:2 & -2


位運算 | (或)

規則:二進位對應位兩兩進行邏輯或運算(對應位中有一 個為1則為1) 即0|0=0,0|1=1,1|1=1

例如:2 | -2


位運算 ^ (異或)

規則:二進位對應位兩兩進行邏輯XOR (異或) 的運算(當對應位的值不同時為 1, 否則為 0)即0^0=0, 0^1=1, 1^1=0

例如:2 ^ -2


按位取反~

規則:二進位的0變成1,1變成0。


移位運算符

左移運算<<:左移後右邊位補 0

右移運算>>:右移後左邊位補原最左位值(可能是0,可能是1)

右移運算>>>:右移後左邊位補 0

對於左移運算符<<沒有懸念右側填個零無論正負數相當於整個數乘以2。

而右移運算符就有分歧了,分別是左側補0>>>和左側補原始位>>,如果是正數沒爭議左側都是補0,達到除以2的效果;如果是負數的話左側補0>>>那麼數值的正負會發生改變,會從一個負數變成一個相對較大的正數。而如果是左側補原始位(負數補1)>>那麼整個數還是負數,也就是相當於除以2的效果。

下面這張圖可以很好的幫助你理解負數的移位運算符:


到這裡,我想你應該對位運算有了初步的認識,在這裡把上面提到的部分案例執行對比一下讓你看一下可能會理解的更清晰:


位運算小技巧

在這裡有些常用的位運算小技巧。

判斷奇偶數

正常判斷奇數偶數的時候我們會這樣寫:

if( n % 2 == 1)
    // n 是個奇數
}

使用位運算可以這麼寫:

if(n & 1 == 1){
    // n 是個奇數。
}

其核心就是判斷二進位的最後一位是否為1,如果為1那麼結果加上2^0=1一定是個奇數,否則就是個偶數。

交換兩個數

對於傳統的交換兩個數,我們需要使用一個變量來輔助完成操作,可能會是這樣:

int team = a;
a = b;
b = team;

但是使用位運算可以不需要藉助額外空間完成數值交換:

a=a^b;//a=a^b
b=a^b;//b=(a^b)^b=a^0=a
a=a^b;//a=(a^b)^(a^b^b)=0^b=0

原理已經寫在注釋裡面了,是不是感覺非常diao呢?

二進位枚舉

在遇到子集問題的處理時候,我們有時候會藉助二進位枚舉來遍歷各種狀態(效率大於dfs回溯)。這種就屬於排列組合的問題了,對於每個物品(位置)來說,就是使用和不使用的兩個狀態,而在二進位中剛好可以用1和0來表示。而在實現上,通過枚舉數字範圍分析每個二進位數字各符號位上的特徵進行計算求解操作即可。


二進位枚舉的代碼實現為:

for(int i = 0; i < (1<<n); i++) //從0~2^n-1個狀態
{
  for(int j = 0; j < n; j++) //遍歷二進位的每一位 共n位
  {
    if(i & (1 << j))//判斷二進位數字i的第j位是否存在
    {
      //操作或者輸出
    }
  }
}

位運算經典問題

有了上面的位運算基礎,我們怎麼用位運算處理實際問題呢?或者有哪些經典的問題可以用位運算來解決呢。

不用加減乘除做加法

題目描述

寫一個函數,求兩個整數之和,要求在函數體內不得使用+、-、*、/四則運算符號。

分析:這道題咋一聽可能沒啥思路,簡單研究一下位運算還是能獨立推出來和理解的。

當然,解決這題前,需要了解上面的四種位運算。還要知道二進位的運算:0+0=0,0+1=1,1+1=0(進位)

對於加法的一個二進位運算。如果不進位那麼就是非常容易的。這時候相同位都為0則為0,0和1則為1.滿足這種運算的異或(不相同取1,相同取0)和或(有一個1則為1)都能滿足.但事實肯定有進位的運算啊!看到上面操作的不足之後,我們肯定還需要解決進位的問題對於進位的兩數相加,這種核心思想為

用兩個數,一個正常m相加(不考慮進位的)。用異或a^b就是滿足這種要求,先不考慮進位(如果沒進位那麼就是最終結果)。另一個專門考慮進位的n。兩個1需要進位。所以我們用a&b與記錄需要進位的。但是還有個問題,進位的要往上面進位,所以就變成這個需要進位的數左移一位。然後就變成m+n重新迭代開始上面直到不需要進位的(即n=0時候)。

實現代碼為:

public class Solution {
     public int Add(int num1,int num2) {
  /*
   *  5+3   5^3(0110)   5&3(0001) 
   *  0101    
   *  0011 
   */
  int a=num1^num2;
  int b=num1&num2;
  b=b<<1;
  if(b==0)return a;
  else {
   return Add(a, b);
  }        
  }
}

當然,這裡也可以科普一下二進位求加法:average = (a&b) + ((a^b)>>1) ;

二進位中1的個數

這是一道經典題,在劍指offer上也有對應題目,其具體題目要求輸入一個整數,輸出該數二進位表示中1的個數(其中負數用補碼表示)

對於這個問題,不用位運算將它轉成二進位字符串直接枚舉字符'1'的個數也可以直接求出來,但是這樣做是沒有靈魂的並且效率比較差。這裡提供兩種解決思路

法一:大家知道每個類型的數據它的背後實際都是二進位操作。大家知道int的數據類型的範圍是(-2^31,2^31 -1)。並且int有32位。但是並非32位全部用來表示數據。真正用來表示數據大小的也是31位。最高位用來表示正負

首先要知道:

其次還要知道位運算&與。兩個十進位與運算.每一位同1為1。所以我們用2的正數次冪與知道的數分別進行與運算操作。如果結果不為0,那麼就說明這位為1.(前面31個都是大於0的最後一個與結果是負數但是如果該位為1那麼結果肯定不為0)


具體代碼實現為:

public int NumberOf1(int n) {
  int va=0;
  for(int i=0;i<32;i++)
  {
    if((n&(1<<i))!=0)
    {           
      va++;
    }
  }
  return va;       
}

法二是運用n&(n-1)。n如果不為0,那麼n-1就是二進位第一個為1的變為0,後面全為1.這樣的n&(n-1)一次運算就相當於把最後一個1變成0.這樣一直到運算的數為0停止計算次數就好了,如下圖共進行三次運算那麼n的二進位中就有三個1。實現代碼為:

public class Solution {
    public int NumberOf1(int n) {
    int count=0;
    while (n!=0) {
     n=n&(n-1);
     count++;
    }
    return count;
 }
}

只出現一次的(一個)數字①

問題描述:

給定一個非空整數數組,除了某個元素只出現一次以外,其餘每個元素均出現兩次。找出那個只出現了一次的元素。

說明:你的算法應該具有線性時間複雜度。你可以不使用額外空間來實現嗎?

分析:

這是一道簡單的面試題,面試官常問怎麼樣用不太複雜的方法找出數組中僅出現一次的數字(其他均出現兩次),暴力枚舉或者使用其他的存儲結構都不夠優化,而這個問題最高效的答案就是使用位運算。首先你要注意兩點:

具體的操作就是用0開始和數組中每個數進行異或,得到的值和下個數進行異或,最終獲得的值就是出現一次(奇數次)的值。


class Solution {
    public int singleNumber(int[] nums) {
        int value=0;
        for(int i=0;i<nums.length;i++)
        {
            value^=nums[i];
        }
        return value;
    }
}

只出現一次的(一個)數字②

問題描述:

給定一個非空整數數組,除了某個元素只出現一次以外,其餘每個元素均出現了三次。找出那個只出現了一次的元素。

說明:你的算法應該具有線性時間複雜度。你可以不使用額外空間來實現嗎?

分析:

這題和上一題的思路略有不同,這題其他數字出現了3次,那麼我們如果直接使用位運算異或操作的話無法直接找到結果,就需要巧妙的運用二進位的其他特性:判斷整除求餘操作。即判斷所有數字二進位1的總個數和0的總個數一定有一個不是三的整數倍,如果0不是三的整數倍那麼就說明結果的該位二進位數字為0,同理否則為1.


在具體的操作實現上,問題中給出數組中的數據在int範圍之內,那麼我們就可以在實現上可以對int的32個位每個位進行依次判斷該位1的個數求餘3後是否為1,如果為1說明結果該位二進位為1可以將結果加上去。最終得到的值即為答案。

具體代碼為:

class Solution {
    public int singleNumber(int[] nums) {
        int value=0;
        for(int i=0;i<32;i++)
        {
            int sum=0;
            for(int num:nums)
            {
                if(((num>>i)&1)==1)
                {
                    sum++;
                }
            }
            if(sum%3==1)
                value+=(1<<i);
        }
        return value;
    }
}

只出現一次的(兩個)數字③

題目描述

一個整型數組裡除了兩個數字之外,其他的數字都出現了兩次。請寫程序找出這兩個只出現一次的數字。

思路

上面的問題處理和理解起來可能比較容易,但是這個問題可能稍微複雜一點,但是這題可以通過特殊的手段轉化為上面只出現一次的一個數字問題來解決,當然核心的位運算也是異或^。

具體思路就是想辦法將數組邏輯上一分為二!先異或一遍到最後得到一個數,得到的肯定是a^b(假設兩個數值分別為a和b)的值。在看異或^的屬性:不同為1,相同為0. 也就是說最終這個結果的二進位為1的位置上a和b是不相同的。而我們可以找到這個第一個不同的位,然後將數組中的數分成兩份,該位為0的進行異或求解得到其中一個結果a,該位為1的進行異或求解得到另一個結果b。

具體可以參考下圖流程:


實現代碼為:

public int[] singleNumbers(int[] nums) {
    int value[]=new int[2];
    if(nums.length==2)
        return  nums;
    int val=0;//異或求的值
    for(int i=0;i<nums.length;i++)
    {
        val^=nums[i];
    }
    int index=getFirst1(val);
    int num1=0,num2=0;
    for(int i=0;i<nums.length;i++)
    {
        if(((nums[i]>>index)&1)==0)//如果這個數第index為0 和num1異或
            num1^=nums[i];
        else//否則和 num2 異或
            num2^=nums[i];
    }
    value[0]=num1;
    value[1]=num2;
    return  value;
}

private int getFirst1(int val) {
    int index=0;
    while (((val&1)==0&&index<32))
    {
        val>>=1;// val=val/2
        index++;
    }
    return index;
}

結語

當然,上面的問題可能有更好的解法,也有更多經典位運算問題將在後面歸納總結,希望本篇的位運算介紹能夠讓你有所收穫,對位運算能有更深一點的認識。


相關焦點

  • C語言|位運算(與運算,或運算,異或運算,取反,移位運算)
    與運算 &為按位與0的二進位補碼000000001的二進位補碼0000000115的二進位補碼00001111-1的二進位補碼111111112.或運算 |為按位或3. 異或運算 ^為按位異或,相同為0,不同為14. 取反 ~為按位取反5.
  • 老婆餅裡沒老婆之用位運算實現加減乘除
    眾所周知,老婆餅裡是沒有老婆的,夫妻肺片是裡沒有夫妻的,那麼四則運算中可以沒有 +、-、×、÷ 這四個運算符嗎?當然是可以的。本文會先從基礎知識介紹起,再通過代碼實現,最後,我們還可以探討一下計算機運算器的起點及其哲學。
  • C語言丨關於位運算的使用,只需掌握這4個簡單示例!
    位運算是指按二進位進行的運算。在系統軟體中,常常需要處理二進位位的問題。C語言提供了6個位操作運算符。這些運算符只能用於整型操作數,即只能用於帶符號或無符號的char,short,int與long類型。
  • 「python opencv視覺零基礎實戰」七邏輯運算應用
    and_img=cv2.bitwise_and(img3,img1),在這一串代碼中對img3與img1進行了邏輯與運算。這時黑色區域與img3圖片的通道區域值進行計算,那就是0與一個內容值進行邏輯與計算,那麼結果為0,img1的文字部分值為1,與img3圖片相同的位置進行邏輯與計算,那麼保留結果。這時,運算後的圖片則應該是生成一張帶有「我是1_bit」字樣的圖片,並且在字樣區域內帶有img3圖片內容。
  • 數學運算方法應用———題型分類精析
    因此,對於行測而言,技巧和方法是非常重要的。為了幫助考生提高數學運算部分的答題速度和得分,本章根據該部分所使用技巧的不同,將數學運算的考題進行了分類。而對於每種題型,本著既要知其然又要知其所以然的目的,我們都將從常規解題方法和技巧性解題方法兩部分進行系統的講解,以便幫助考生更好地理解和運用該部分的原理和技巧。
  • 每秒14.86億億次運算速度的超級計算機,究竟用什麼作業系統?
    近幾年來,我們頻頻聽到AI醫療、自動駕駛、人工智慧大戰圍棋高手等詞彙或事件,其實在它們背後,有我們看不到的龐然大物,它性能秒殺個人電腦,運算速度通常能超過每秒一萬億次,它就是超級計算機。那麼,你知道超級計算機都在使用什麼作業系統嗎?超級計算機是計算機中功能最強、運算速度最快、存儲容量最大的一類計算機。
  • 谷歌大腦提出AutoML-Zero,只會數學運算就能找到AI算法|開源
    曉查 發自 凹非寺量子位 報導 | 公眾號 QbitAIAlphaGo戰勝了人類最強棋手,但前提是它先學會了人類棋譜,離不開人類指導。當然可以,谷歌大腦團隊最新的研究成果已經做到了。谷歌將這種技術稱之為AutoML-Zero,意為「從零開始的自動機器學習」,已經在GitHub開源,並在Arxiv上提交了論文。而且這一研究還是來自谷歌大腦的Quoc V.Le大神之手。
  • EXCEL知識:數組運算是什麼?
    雖然會用數組寫出來,但是很多時候完全不理解為什麼要那樣寫。後來我看了一些資料,然後重複聽老師講課,最後慢慢理解了。到現在也有幾個月的時間沒有再看,但是最近做了一些有關數組方面的例題,發現很多以前無法理解的問題,現在豁然開朗。可能是過了一段時間再去看,忽然就理解了很多以前無法理解的問題。下面我主要介紹一些我自己理解的數組運算。
  • 如何進行四則運算的?
    我們知道,人類進行運算的本質是查表,並且我們存儲的表是有限的。那麼,計算機是怎樣進行四則運算的呢,也是查表嗎?肯定不是。 例如A輸入低電平、B輸入高電平,那麼Q就會輸出高電平;轉化為二進位就是A輸入0、B輸出1,那麼Q就會輸出1,對應的C語言運算表達式為0||1=1。
  • CPU運算電路:電晶體如何表示0和1
    例如A輸入低電平、B輸出高電平,那麼Q就會輸出低電平,轉換為二進位就是A輸入0、B輸出1,那麼Q就會輸出0,對應的C語言運算表達式為0&&1=0。 例如A輸入低電平、B輸入高電平,那麼Q就會輸出高電平,轉化為二進位就是A輸入0、B輸出1,那麼Q就會輸出1,對應的C語言運算表達式為0||1=1。
  • 如何兩分鐘搞定雙敏感性模擬運算?
    拿不拿地確實有太多不可控的外界因素,我們能做到就是觀察形勢同時管好自己。話說回來,如果土地送到眼前了,小夥伴們能不能接住呢? 假如公司突然戰略一變,需要擴張,拋給你一塊地,讓你快速出具專業的投決文件,能不能迅速而專業的搞定?今天老嶽分享一個非常實用的小工具模型,幫助大家完成專業的測算。
  • 如何理解C語言中的移位運算?它們的用途是什麼?
    左移:如果把a<<4,即把[01100011]進行左移4位,這樣a就會變成[00110000]。這是如何變成的呢?這是把[01100011]最左邊的那4位砍掉,在剩下的4位的右邊補充4個零。
  • 第七講 二次根式的運算
    第七講   二次根式的運算   同類二次根式,有理化是二次根式中重要概念,它們貫穿於二次根式運算的始終,因為二次根式的加減實質就是合併同類二次根式
  • Python從零開始系列連載(6)——Python的基本運算和表達式(下)
    結果發現。。。竟然不一樣。emmm如果是浮點數,%m.nf    m指的是輸出總寬度,小數點之後保留n位(四捨五入保存),如果總寬度超過m,按照實際顯示不知道這個孩子現在到底有多強了比較運算通常是比較兩個數值型或者字符串型數據,然後返回一個布爾值小明:老溼!什麼是布爾值?
  • 2021年中考數學知識點:實數的運算
    中考網整理了關於2021年中考數學知識點:實數的運算,希望對同學們有所幫助,僅供參考。   實數的運算   1、加法:   (1)同號兩數相加,取原來的符號,並把它們的絕對值相加;   (2)異號兩數相加,取絕對值大的加數的符號,並用較大的絕對值減去較小的絕對值。
  • 有理數的混合運算計算技巧!還等什麼,抓緊收藏分享起來,可下載
    開學已經一周有餘,針對於剛剛步入七年級的學生而言,初中的學習多了很多的樂趣,但同時學習的科目也在擴展,就數學而言也開始拓展到了「有理數」範圍,通過一周的學習,相比學生們已經了解了有理數的相關意義和分類,今天我們就主要分享一下有關「有理數的運算」技巧,希望有所幫助。
  • 解密年輕速度締造者,10萬元最速SUV,長安歐尚X5是靠跑得快和運算快
    老牌的AMG做到的事情,長安歐尚的藍鯨NE1.5T發動機也做到了。 首先,藍鯨NE系統發動機在開發的時候,就做到了優化的模塊化頂層設計。例如,在大家最看重的在缸體製造方面,長安歐尚的這臺發動機就達到國際水平。
  • 小學知識點突破——分數的四則運算
    整數的四則運算之前已經學過,知道了相應的運算規則和技巧。整數的運算順序(運算優先級)是:先算乘除後算加減,有括號的先算括號裡面的。原理都是相通的,分數的加、減、乘、除運算也有一定的規則,這次我們將分別對分數的加、減、乘、除,逐一進行講解。
  • 1.9億億次秒浮點運算能力!聯想這麼厲害?
    世界500強超級計算機新名單公布世界500強超級計算機新名單公布,聯想製造的180臺超級計算機入圍,在世界上浮點計算性能最強的500臺超級計算機中,聯想製造的174臺超級計算機入圍,數量遠遠超過其他製造商,再次在全球高性能計算提供商的份額中排名第一,排在第三位的是Sierra,這是位於加利福尼亞州的勞倫斯·利弗莫爾國家實驗室的系統
  • 初二上學期,勾股定理的運算,注意分情況討論
    在勾股定理的運算中,有些題目運算量較大,需要掌握運算的技巧,如果選擇死算不僅計算上繁瑣,還很容易算錯,或者即使算對了也可能得不到正確的答案。在勾股定理的運算中,還會出現較多的分情況討論問題,一不留神,可能就會出現漏解。