找到數組中出現特定次數數字的問題


作者:Grey

原文地址:找到數組中出現特定次數數字的問題

問題一

一個數組中有一種數出現了奇數次,其他數都出現了偶數次,怎么找到並打印這種數?

牛客-NowCoder_EvenOddTimes

LeetCode_0136_SingleNumber

解題思路

因為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;
    }
}

問題二

一個數組中有兩種數出現了奇數次,其他數都出現了偶數次,怎么找到並打印這兩種數

LeetCode_0260_SingleNumberIII

解題思路

根據問題一的結論,假設數組中ab這兩個數出現了奇數次,這個數組中的所有數字異或以后得到的結果一定a^b

因為ab是兩種不同的數,所以a^b的結果一定不等於0。

所以,a^b的結果如果轉換成二進制的話,一定有某位是1

我們假設a^b轉換成二進制后最右側位置的1在i位置,由此可以得出一個結論:a和b的二進制在i位置一定一個為0,一個為1

不妨假設ai位置為0,bi位置為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)

我們還可以解決如下問題:

LeetCode_0191_NumberOfOneBits

思路:即提取出最右側的1以后,與目標數進行與運算, 得到一個新的目標數,然后繼續提取新目標數的最右側的1,如此往復,即可把所有位置的1都提取出來。

問題三

一個數組中有一種數出現k次,其他數都出現了m次,m > 1, k < m, 找到出現了k次的數

要求:假設數組中所有數都是int類型,額外空間復雜度O(1),時間復雜度O(N)

LeetCode_0137_SingleNumberII

NowCoder_FindOneInK

我們可以這樣考慮,設置一個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位置的值之和。

然后i0位置開始拿出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;
 }

更多

算法和數據結構筆記

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM