找到陣列中出現特定次數數字的問題
問題一:一個陣列中有一種數出現了奇數次,其他數都出現了偶數次,怎麼找到並列印這種數
問題一解題思路
因為a ^ a = 0
, 所以出現過偶次的數異或結果都是0
,又因為0^a=a
,所以把陣列中所有的數做異或
以後的結果,就是出現了奇數次的那個數。
問題一完整程式碼
public class LeetCode_0136_SingleNumber {
public static int singleNumber(int[] nums) {
int ans = nums[0];
for (int i = 1; i < nums.length; i++) {
ans ^= nums[i];
}
return ans;
}
}
問題二:一個陣列中有兩種數出現了奇數次,其他數都出現了偶數次,怎麼找到並列印這兩種數
問題二解題思路
根據問題一的結論,假設陣列中a
和b
這兩個數出現了奇數次,這個陣列中的所有數字異或以後得到的結果一定a^b
因為a
和b
是兩種不同的數,所以a^b
的結果一定不等於0。
所以,a^b
的結果如果轉換成二進位制的話,一定有某位是1。我們假設a^b
轉換成二進位制後最右側位置的1在i位置,由此可以得出一個結論:a和b的二進位制在i位置一定一個為0,一個為1
不妨假設a
的i
位置為0
,b
的i
位置為1
。
此外,容易得知,整個陣列中的數,i
位置為0
的數除了a
以外,其他數一定有偶數個, i
位置為1
的數除了b
之外,其他數一定有偶數個。
那麼我們可以只對i
位置為1
的數求異或,最後得到的值一定是b
,然後通過b^(a^b) = a
,可以得到a
的值。
最後只剩下一個問題:
如何求一個數最右側的1呢?
假設 某個數x二進位制為:00010010
, 其最右側的1是:00000010
。
演算法是:對於一個數x
來說,它最右側的1
等於x & ((~x) + 1)
或者x & (-x)
所以,如果一個數是a^b
,那麼它最右側的1
就是(a^b) & (~(a^b) + 1)
我們用(a^b) & (~(a^b) + 1)
這個值去和陣列中每個值數做與運算(&),如果與完以後的結果是0
,說明這個數i
位置是0
,否則說明這個數i
位置是1
。我們前面已經得到一個結論,i
位置為0
的數除了a
以外,其他數一定有偶數個。所以,用(a^b) & (~(a^b) + 1)
這個值和每個i
位置是0
的陣列元素做與運算以後,最後的結果一定是a
。 得到a
以後,然後通過a^(a^b) = b
,可以得到b
的值。
問題二完整程式碼
public class LeetCode_0260_SingleNumberIII {
public static int[] singleNumber(int[] arr) {
int eor = 0;
for (int n : arr) {
eor ^= n;
}
// 假設出現奇數次的兩種數為 a和b
// eor = a ^ b
// 獲取最右側的1
int a = 0;
int rightOne = eor & ((~eor) + 1);
for (int n : arr) {
if ((n & rightOne) == 0) {
a ^= n;
}
}
int b = a ^ eor;
return new int[]{a, b};
}
}
當有如下公式計算一個數最右側的1
以後
x & ((~x) + 1)
我們還可以解決如下問題:
思路:即提取出最右側的1
以後,與目標數進行與運算(&), 得到一個新的目標數,然後繼續提取新目標數的最右側的1
,如此往復,即可把所有位置的1
都提取出來。
問題三 一個陣列中有一種數出現k次,其他數都出現了m次,m > 1, k < m, 找到出現了k次的數
要求:假設陣列中所有數都是int型別,額外空間複雜度O(1),時間複雜度O(N)
問題三解題思路
我們可以這樣考慮,設定一個32位的陣列,
int[] help = new int[32];
遍歷原始陣列中每個數num的每一個二進位制位, 虛擬碼如下:
for (int num : arr) {
for (int i = 0; i < 32; i++) {
help[i] += num的二進位制中i位置的值(只能是0或者1)
}
}
經過以上迴圈,help
陣列就把陣列中的所有數的二進位制位上的資訊累加起來了。
help[0]
表示陣列中所有數二進位制中0位置的值之和;
help[1]
表示陣列中所有數二進位制中1位置的值之和;
......
help[31]
表示陣列中所有數二進位制中31位置的值之和。
然後i
從0
位置開始拿出help[i]
的值,假設help[i]=x
,用x % m
, 如果結果是k
,說明出現k
次的元素在這個位置上是1
, 否則,這個出現了k
次的數在i
位置上是0
, 遍歷完help
陣列,出現k
次元素的每一位資訊都拿到了,然後還原出來即可。
問題三完整程式碼
public static int km(int[] arr, int k, int m) {
int[] helper = new int[32];
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < 32; j++) {
helper[j] += ((arr[i] >> j) & 1);
}
}
int ans = 0;
for (int i = 0; i < 32; i++) {
if (helper[i] % m == k) {
ans |= (1 << i);
}
}
return ans;
}