java 演算法題 - 面試中常見的位操作演算法題
前言
上一篇部落格 聊一聊 Android 中巧妙的位操作 中,我們講解了 java 中常用的位運算及常用應用場景,今天,讓我們一起來看一下,面試中常見的位操作的演算法題。
兩個只出現一次的數字
【題目描述】一個整型數組裡除了兩個數字之外,其他的數字都出現了兩次。請寫程式找出這兩個只出現一次的數字。
看到這道題目,先思考一下,你會怎麼做?
不熟悉位運算性質的同學,很多人第一時間可能都有這樣的想法
遍歷陣列,記錄下陣列中每個數字的出現次數,再找到那兩個值出現一次的數字。
這裡我們以 ArrayList 為例子,進行解答,思路大概如下
- 遍歷陣列,使用一個 ArrayList 記錄當前只出現了一次的值。
- 若當前遍歷的值,在 ArrayList 中已經出現,則移除該值,繼續遍歷。
- 最後剩下的兩個值,即為所求。
於是我們可以快速寫出以下的程式碼。
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];
}
相關推薦
參考部落格
相關推薦
最後的最後,賣一下廣告,歡迎大家關注我的微信公眾號,掃一掃下方二維碼或搜尋微訊號 Android 技術人,即可關注。 目前專注於 Android 開發,主要分享 Android開發相關知識和一些相關的優秀文章,包括個人總結,職場經驗等。