1. 程式人生 > >關於幾個java位運算的算法分析

關於幾個java位運算的算法分析

這一 數組 blog 回歸 pan mask turn 判斷 二進制表示

問題一:

給定一個正整數N,求其二進制形式的第一個比特位1(從低位到高位的順序)。

例如,給定正整數12,其低8位二進制表示為:00001100

從低位到高位的順序,第一個1出現在第三位。

版本一:

最低位開始,針對每一位進行與(&)操作判斷是否為1,直到遇到第一個1為止。

算法實現(版本一):

    public static int getFirstBit1(int N){
     int mask = 1; int pos = 1; while(mask <= N){ if((N&mask) == mask){
break; }else { mask <<= 1; pos++; } } return pos; }

版本二:

假定第一個1出現在第pos位,註意到tmp=(N^(N-1))+1實際上代表pos位及其右邊全部置1並且pos的左邊全部置0的那個數,求pos的值問題轉化為求tmp有多少個1的問題。

    public static int getFirstBit1(int N){
        int tmp = (N^(N-1))+1;//代表pos位及其右邊全部置1並且pos的左邊全部置0的那個數
        
int pos = 0; while(tmp > 1){ tmp >>= 1; pos++; } return pos; }

問題二:

給定一正整數N,求其二進制形式中比特位為1的個數。

版本一:

依次從低位到高位校驗每一位是否為1並計數。

    /*常規的算法*/
    public static int getNumberOfBit1(int N){
        int count = 0;
        int mask = 1;
        while(mask <= N){
            System.out.println(
"ok"); if((N&mask) == mask){ count++; } mask <<= 1; } return count; }

版本二:

註意到(N-1)&N的結果必然會將N的最右邊的1消掉,以N=12的低8位舉例:

12:00001100

12-1:00001011

12&11:00001000

其結果就是將12的最右邊的1消掉。

按照如此規律依次循環直到N為0為至,經歷了多少次循環,代表原數N中有多少個1。

    /*較為高效的算法*/
    public static int getNumberOfBit1(int N){
        int count = 0;
        while(N != 0){
            count++;
            N = (N-1)&N;
            System.out.println("ok");
        }
        return count;
    }

問題三:

一個無序數組裏有若幹個正整數,其中有一個整數出現了奇數次,其余整數都出現了偶數次,找出這個出現奇數次的整數。

例如,給定無序數組:

int[] ary = {2,3,6,3,4,9,4,2,6};

出現奇數次的整數為:9

分析:

異或運算滿足交換性質,比如2^3^2^3的結果和2^2^3^3的結果是一樣的。

註意到偶數次的現象,因此,可以考慮異或運算:當兩位相同時返回0,不同是返回1。

這一規律表現在:對於一個整數,重復偶數次,對其進行異或運算的結果必然為0;

更進一步,對以上現象的疊加,表現在:對多個整數,每個整數都出現偶數次,對其進行異或運算的結果也必然為0,而與其出現的順序性無關;

算法實現:

    public static int find(int[] ary){
        int tmp = 0;
        for (int i = 0; i < ary.length; i++) {
            tmp = tmp^ary[i];//依次進行異或運算
        }
        return tmp;
    }

問題四(問題三的擴展):

一個無序數組裏有若幹個正整數,若其中有兩個整數出現了奇數次,其余整數都出現了偶數次,找出這個出現奇數次的兩個整數並返回其和。

例如,給定無序數組:

int[] ary = {2,3,6,3,4,9,4,2,1,6};

出現了奇數次的兩個整數為:9和1,返回其和10。

分析:

假設出現了奇數次的兩個整數為A和B;

一個中心思想就是將A和B分開到不同的兩個組,此時的情況回歸到問題三的情形。分別對這兩個組中的元素依次做異或運算,將會分別得到A和B。

現在的問題是怎麽來分組?

按照問題三的思路:隊原數組ary依次進行異或操作後得到的結果必然等價於A^B的結果,同時註意到A與B不相等,因此A^B的結果必不為0。

既然A^B不為零,則其二進制表示形式中必然存在1,就是由於這個1的存在,可以區分A和B,因為只有A和B在這個位的值不相同時,其異或結果的對應位才能為1。

選取A^B結果的二進制表示形式中任何一個1(比如選擇最右邊的1)作為分組條件,在遍歷ary數組時,首先進行分組判斷,分組後在各自做異或運算即可。

算法實現:

    public static int find(int[] ary){
        int tmp = 0,mask;
        int targetOne = 0,targetTwo = 0;
        for(int ele:ary){
            tmp = tmp^ele;
        }
        int pos = getFirstBit1(tmp);//以tmp的最右邊變的1作為分組條件
        mask = 1<<(pos-1);//這裏的mask實際上就是一個比特掩碼,只有pos位為1 ,其余位均為0
        for (int i = 0; i < ary.length; i++) {
            if ((ary[i]&mask) ==  mask) {//說明ary[i]的pos位為1
                targetOne = targetOne^ary[i];
            }else {//說明ary[i]的pos位為0
                targetTwo = targetTwo^ary[i];
            }
        }
        return targetOne+targetTwo;
    }

轉載註明原文地址:https://i.cnblogs.com/EditPosts.aspx?postid=7689800

關於幾個java位運算的算法分析