1. 程式人生 > >java 演算法題 - 面試中常見的位操作演算法題

java 演算法題 - 面試中常見的位操作演算法題

前言

上一篇部落格 聊一聊 Android 中巧妙的位操作 中,我們講解了 java 中常用的位運算及常用應用場景,今天,讓我們一起來看一下,面試中常見的位操作的演算法題。


兩個只出現一次的數字

【題目描述】一個整型數組裡除了兩個數字之外,其他的數字都出現了兩次。請寫程式找出這兩個只出現一次的數字。

看到這道題目,先思考一下,你會怎麼做?

不熟悉位運算性質的同學,很多人第一時間可能都有這樣的想法

遍歷陣列,記錄下陣列中每個數字的出現次數,再找到那兩個值出現一次的數字。

這裡我們以 ArrayList 為例子,進行解答,思路大概如下

  1. 遍歷陣列,使用一個 ArrayList 記錄當前只出現了一次的值。
  2. 若當前遍歷的值,在 ArrayList 中已經出現,則移除該值,繼續遍歷。
  3. 最後剩下的兩個值,即為所求。

於是我們可以快速寫出以下的程式碼。

public class Solution {

    public void FindNumsAppearOnce(int[] array, int num1[], int num2[]) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        for (int i = 0; i < array.length; i++) {
            if (!list.contains(array[i]))
                list.add(array[i]);
            else
                list.remove(new Integer(array[i]));
        }
        if (list.size() > 1) {
            num1[0] = list.get(0);
            num2[0] = list.get(1);
        }
    }
}

想一下,這樣的時間複雜度和空間複雜度是多少呢?

我們容易得出時間複雜度為 O(n), 空間複雜度也為 O(n)。那有沒有更優的解法呢?

我們回頭想一下,在上一篇部落格 聊一聊 Android 中巧妙的位操作 中,我們講到異或運算子,若位上相同,則為 0 ,位上不同,則為 1。既然陣列中其他數字都能出現兩次,只有兩個數字出現一次,那麼我們遍歷陣列,進行異或之後,異或之後得出的結果為這兩個數(只出現一次)的異或異或結果。

為什麼呢?答案很簡單,兩個相同的數進行異或之後,結果為 0,而任何一個數與 0 異或結果等於他本身。

得出這兩個數的異或結果之後又什麼用呢?想一下,異或的特徵,位上相同則為 0,位上不同則為 1.由於這兩個數不同,那麼這兩個數的異或結果肯定不為 0,即至少存在某一位為 1.

因此,我們可以找出第一位為 1 的位數,然後根據這一位是否為 1,將陣列分為兩組,分別進行異或,異或結束後即為我們所求的結果。

public class Solution {
    public void FindNumsAppearOnce(int[] array, int[] num1, int[] num2)    {
        int length = array.length;
        if(length == 2){
            num1[0] = array[0];
            num2[0] = array[1];
            return;
        }
        int bitResult = 0;
        for(int i = 0; i < length; ++i){
            bitResult ^= array[i];
        }
        int index = findFirst1(bitResult);
        for(int i = 0; i < length; ++i){
            if(isBit1(array[i], index)){
                num1[0] ^= array[i];
            }else{
                num2[0] ^= array[i];
            }
        }
    }

    private int findFirst1(int bitResult){
        int index = 0;
        while(((bitResult & 1) == 0) && index < 32){
            bitResult >>= 1;
            index++;
        }
        return index;
    }

    private boolean isBit1(int target, int index){
        return ((target >> index) & 1) == 1;
    }
}


求出被去掉的兩個數

[題目描述] 給你1-1000個連續自然數,然後從中隨機去掉兩個,再打亂順序,要求只遍歷一次,求出被去掉的兩個數。

  • 第一種方法:使用方程組進行解決

遍歷被打亂的陣列時,計算value的累加值和value平方的累加值。結合未打亂之前的陣列,這樣就能得出 x + y = m 與 xx + yy = n兩個方程,解這組方程即可算出被去掉的兩個數。這種方法比較容易理解,實現起來也比較簡單

x + y  =  m
xx + yy  =  n

這種解法只需遍歷陣列一次,時間複雜度為 O(n)

  • 第二種解法:使用異或解決

解法基本跟上面的題目一樣,這裡說一下思路.

  • 將這個陣列與 0-1000 這 n 個連續自然數進行異或,得到這兩個去掉的數的異或值
  • 再找出這個異或值第 1 位為 1 的位數,標記為 N
  • 在遍歷這個陣列,根據第 N 位是否為 1,分為兩組進行異或

這種解法需要遍歷陣列兩次,時間複雜度為 O(n)


在其他數都出現三次的陣列中找到只出現一次的數

出現三次或者三次以上去找那個單獨的值的時候該怎麼辦呢?好像不能用異或了,但是考慮到輸入是 int 型陣列,所以可以用32位來表達輸入陣列的元素。

假設輸入中沒有 single number,那麼輸入中的每個數字都重複出現了數字,也就是說,對這 32 位中的每一位i而言,所有的輸入加起來之後,第 i 位一定是 3 的倍數。

現在增加了single number,那麼對這 32 位中的每一位做相同的處理,也就是說,逐位把所有的輸入加起來,並且看看第 i 位的和除以 3 的餘數,這個餘數就是 single number 在第 i 位的取值。這樣就得到了 single
number 在第i位的取值。這等價於一個模擬的二進位制,接著只需要把這個模擬的二進位制轉化為十進位制輸出即可。

另外,這個做法可以擴充套件,如果有一堆輸入,其中 1 個數字出現了 1 次,剩下的數字出現了 K 次,這樣的問題全部可以使用這樣的辦法來做。

在其他數都出現k次的陣列中找到只出現一次的數

public class SingleNum {
    public static void main(String[] args) {
		SingleNum s=new SingleNum();
		int[] arr= {2,2,2,5,2,3,4,5,4,5,4,5,4};
		System.out.println(s.singleNumber(arr,4));
	}
	
    public static int singleNumber(int A[],int k) {  
        int n=A.length;
        int[] count=new int[32];  
        int result=0;  
        for(int i=0;i<32;i++){  
            for(int j=0;j<n;j++){  
            	count[i]+=((A[j]>>i)&1);
            	//首先把輸入數字的第i位加起來,這裡和1去與,取到的就是一位
            }  
            count[i]=count[i]%k; 
              //然後求它們除以k的餘數
            result|=(count[i]<<i);//把二進位制表示的結果轉化為十進位制表示的結果  
        }  
        return result;
    }
}

第二種解法

既然其他數字都出現 n 次 (n > 1),只有一個數字出現 1 次, 那麼我們可以先對陣列進行排序,接著去遍歷陣列,對於中間的數字考慮是否前後有相同的,對於第一數字單獨處理

import java.util.*;
 
public class Solution {
    public int singleNumber(int[] A) {
        int i=0;
        Arrays.sort(A);
        for(i=0;i<A.length-1;i++){
            if(i==0){
                if(A[i]==A[i+1]){continue;}
            }else{
                if(A[i]==A[i+1]||A[i]==A[i-1]){
                continue;
                 }
            }
            break;
        }
        return A[i];
    }


相關推薦

參考部落格

在每個數字都出現K次的陣列中,找只出現一次的數

相關推薦

聊一聊 Android 中巧妙的位操作

二分查詢的相關演算法題

快速排序的相關演算法題(java)

最後的最後,賣一下廣告,歡迎大家關注我的微信公眾號,掃一掃下方二維碼或搜尋微訊號 Android 技術人,即可關注。 目前專注於 Android 開發,主要分享 Android開發相關知識和一些相關的優秀文章,包括個人總結,職場經驗等。