數組中只出現一次的數字:輸入一個數組,該數組中有兩個數字只出現了一次,其他數字都出現了兩次,求出這兩個只出現了一次的數字
要求時間複雜度爲O(n)空間複雜度爲O(1)
考慮一個數組中只有一個數字僅僅出現一次而其他數字都出現了兩次,求這個只出現了一次的數字 – 結合異或運算的性質,兩個相同數字異或的結果必爲0
那麼我們只需要依次 將數組中的數據進行異或,最終結果就是那個只出現一次的數字
於是原問題就轉化爲如何將原數組分爲兩個數組,使得兩個數組中各有一個值出現一次的數字,而且相同的數字都在同一個數組中
考慮到一個數字的二進制位中,要麼爲0要麼爲1,而相等數字的指定位必相同,於是就可以根據指定位是0還是1將原數組劃分爲兩個數組,且滿足上述的要求
另外,將兩個不相同數字區分開(因爲可能存在兩個不相同數字的指定位是相同的)的方法是,將數組中所有的數字異或之後,結果實際上等於這兩個不相等的數的異或結果
於是可以從這個結果的二進制中取出一位非零位,以該位爲標準,就可以將兩個不相等的數區分開了(因爲根據異或運算這個不爲0的位對應於這兩個數相同的位,該位置必不相同)
public class _Q40<T> {
public void FindNumbersAppearOnce(int nums[]){
if(nums == null) return;
if(nums.length < 4) return;
int resultExclusiveOr = 0;
for(int i=0; i<nums.length; i++){
resultExclusiveOr ^=nums[i];
}
int count = 0;
while(true){ // 找到異或結果中,從右到左第一個不爲0 的位,用於數組劃分
if((resultExclusiveOr & 0x1) == 0){
count++;
resultExclusiveOr = (resultExclusiveOr>>1);
}else{
break;
}
}
List<Integer> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
for(int i=0; i<nums.length; i++){
if(((nums[i]>>count) & 0x1) == 0) // 負數其實也不太需要單獨考慮
list1.add(nums[i]);
else
list2.add(nums[i]);
}
int result1 = core(list1);
int result2 = core(list2);
System.out.println("first : " + result1);
System.out.println("second : " + result2);
}
private int core(List<Integer> list){
if(list.size() < 3) return Integer.MIN_VALUE; // 非法輸入處理的有點簡單粗暴
int result = 0;
for(int i=0; i<list.size(); i++){
result ^= list.get(i);
}
return result;
}
}
測試代碼:
public class _Q40Test extends TestCase {
_Q40<?> appearOnce = new _Q40();
public void test(){
int nums1[] = {2, 4, 3, 6, 3, 2, 5, 5};
appearOnce.FindNumbersAppearOnce(nums1);
}
}