上篇談到了用異或來解決,確實是個好方法,時間復雜度為o(n),比例一遍ok,空間復雜度為o(1),只占用一個空間足矣。現在把這個問題升級下:
(1)給出n個數,其中有且僅有一個出現了奇數次,其余的都出現了偶數次。用線性時間常數空間找出這個出現奇數次的數
(2)給定n個數,其中有且僅有兩個出現了奇數次,其余的都出現了偶數次。用線性時間常數空間找出這兩個出現奇數次的數
原理(原理不是很懂的,先看看上篇)
- 任何數和自己異或為0
- 任何數和0異或為自己
- 異或具有交換律
思路
(1)一個出現奇數次
- 出現偶數次的一異或為0了,對剩下的奇數次數不造成干擾
- 奇數次(2n+1)的前2n次一異或為0了,對剩下那個數不造成干擾
- 剩下的那個數就是結果
(2)兩個出現奇數次
- 常規的從頭到尾異或一遍,得到數肯定不為0,這個數是那兩個出現奇數次的數異或的結果
- 找出這個數中不為1的那個位pos(在這個位置處,兩個奇數次的數肯定不同——要是相同這個位也是0)
- 整個序列根據位pos的值分成兩組(0的一組,1的一組,這樣把出現偶數次的分到一組,無礙。出現奇數次的分到兩組,正好)
- 對着兩組,利用(1)的方法,解決
細節:如何找到一個二進制中第一個是"1"的位
參考代碼:
int Judge(int val, int j)
{ int i;
for(i=2; i!=j; i*=2)
val /= 2;
return val % 2;
}
#include <stdio.h> int main() { int val[6] = {4, 3, 1, 56, 3, 1}, i, j; int pos = val[0]; int left=0, right=0; //分別表示兩個奇數次數 for (i = 1; i < 6; i++) { pos ^= val[i]; //得出兩奇數次數的異或 } for (j = 2; ; j *= 2) { if (0 != pos % j) //找出開始為1的位置 break; } for (i = 0; i < 6; i++) //根據標志位,分別找出那個奇數次數 { if ( 0 == Judge(val[i], j)) left ^= val[i]; else right ^= val[i]; } printf("%d........%d\n", left, right); return 0; }
另一寫法(記錄移動次數)
#include <iostream> using namespace std; void printOneNum(int *a, int size) { if(size <= 0) return; int tmpsum = 0; for(int i = 0; i < size; ++i) tmpsum ^= a[i]; cout << "hello:" << tmpsum << endl; int movestep = 0; while(tmpsum % 2 == 0) { ++movestep; tmpsum >>= 1; } cout << "movestep:" << movestep << endl; int left = 0, right = 0; for(int i = 0; i < size; ++i) { int tmp = a[i] << movestep; if(tmp % 2 == 0) left ^= a[i]; else right ^= a[i]; } cout << right << " " << left << endl; } int main() { int a[] = {4, 3, 1, 5, 3, 1}; int size = sizeof(a) / sizeof(*a); printOneNum(a, size); }