一個經典且稍有難度的C面試題,值得一看!

題目是這樣的:在一個整數數組中1個數出現了3次,其餘的數都出現了2次,請找出出現3次的數。

建議大家自己先思考一下,我們下面直接給出瞭解法。

一、常規解法(3種)
1.用兩個循環,外層循環每次提供一個數,內層循環遍歷數組進行比對,用另外的變量存儲相等的次數,內層循環結束之後,如果存儲次數的變量等於3,就輸出這個元素,然後退出,否則外層循環提供下一個值,繼續上述過程。

for(i=0;i<SIZE;i++)
{
    n=0;
    for(j=0;j<SIZE;j++)
        if(src[i]==src[j])
            n++;
    if (n==3)
    {
        printf("%d\n",src[i]);
        return;
    }
}
  1. 建立一個臨時數組,初始化爲0,遍歷一遍題目所給數組,將數組對應的值記錄到新數組中,出現一次就++,所給數組的值和新數組的下標一一對應,然後遍歷新數組,讀出記錄的值。
for(i=0;i<SIZE_src;i++)
    tmp[src[i]]++; //臨時數組tmp
for(i=0;i<SIZE_tmp;i++)
    if (tmp[i]==3)
    {
        printf("%d\n",i);//打印下標
        return;
     }
  1. 先用快排排序,再遍歷數組,如果第i個數組元素與第i+1個相同就i+2,如果i與i+1,i+2都相同,就輸出,否則繼續下一輪循環。
for(i=0;i<SIZE;i+=2) {
    if(src[i]==src[i+1]){
          if(src[i]==src[i+2]) {
              printf("%d\n",i); 
              return;}}}

二、最優解
以上3種方法無疑都是可行的,但都不是最優解,有的時間複雜度有問題,有的無法估計邊界,有的代碼太複雜,也不是我們今天想講的。其實本題採用位運算中異或是最簡單的。

講之前要明確幾個知識點:(1)整數與0異或爲本身。與本身異或爲0,即本身的奇數次異或還爲本身,偶數次異或爲0。(2)異或運算符合結合律和交換律。有了以上兩點,本題很簡單了,全部異或後,出現2次數的異或都爲0,只剩出現3次的數了。

int singlenumber(int src[], int size)
{
    int result=0;
    for(int i=0; i<size; i++)
        result ^= src[i]; //遍歷數組全部異或
    return result;
}

三、變形推廣
看到這裏,本題就可推廣爲一個數出現了奇數次,其他數全爲偶數次,找出該數,以上代碼依舊沒有問題。其實還可以有很多經典變形,都是利用位運算解決,但難度有所提高。請看如下兩個變形:

(1)若有兩個數出現了1次,其他數出現了2次,找出這兩個數。(原百度面試題)
(2)若有一個數出現了1次,其他數出現了3次,找出這個數。

我們先看第一題:現在數組中有兩個數字只出現1次,直接異或一次只能得到這兩個數字的異或結果,但光從這個結果肯定無法得到這個兩個數字。如果能將兩個數分開到二個數組中,那顯然符合上述“異或”解法的關鍵點了。因此這個題目的關鍵點就是將這兩個數分開到二個數組中。由於在二進制上必定有一位是不同的。我們只需找出第一位不同的就行,這根據異或的結果就可以知道了,這個結果在二進制上爲1的位都說明這兩個數在這一位上是不同的。

void fun(int src[], int size, int *n1, int *n2)
{
int i, j, result;
//計算這兩個數的異或結果
result = 0;
for (i = 0; i < size; i++)
result ^= src[i];

// 找第一個爲1的位
for (j = 0; j < sizeof(int) * CHAR_BIT; j++)
if (((result >> j) & 1) == 1)
break;
// 這兩個數字在第j位上是不相同的,由此分組即可
*n1 = 0, *n2 = 0;
for (i = 0; i < n; i++){
     if (((src[i] >> j) & 1) == 0)
        *n1 ^= src[i];
else
    *n2 ^= src[i];
}}

再來看第二題:假如二進制位不進位的話,對數組中除了那個數以外的所有數做累加,每一位上的值都是3的倍數。而在加上這個數後,每一位取模3後,不是1就是0,組合起來就是該數了。(當然還有更好的解法,能進一步降低時間複雜度,用到了類似於邏輯電路的相關知識,難度又有所增加,在這裏就不講了)

int singlenumber(int src[], int n) {
      int result = 0;
      int i, j,sum;
      for(i=0;i< sizeof(int) * CHAR_BIT;i++)
      {
          sum = 0;
          for(j=0;j<n;j++)
              sum += ((src[j]>>i)&1); //每輪一位做累加
           if(sum%3 == 1)
           result |= (1<<i);//確定每一位是否爲1
        }
        return result;
    }

四、總結
各位,今天我們講的題目可以總稱爲“尋找出現N次的數”,最本質最核心的其實就是位運算的靈活運用,再怎麼變換,到了最後都是巧用位運算,這也是這類題最終想考察的知識點,希望今天的文章對大家有所啓發、有所幫助,就到這裏吧,感謝耐心閱讀。

其實做爲一個學習者,有一個學習的氛圍跟一個交流圈子特別重要這裏我推薦一個C/C++基礎交流583650410,不管你是小白還是轉行人士歡迎入駐,大家一起交流成長。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章