1. 程式人生 > >leetcode刷題--基礎陣列--只出現一次的數字(C)

leetcode刷題--基礎陣列--只出現一次的數字(C)

  1. 給定一個非空整數陣列,除了某個元素只出現一次以外,其餘每個元素均出現兩次。找出那個只出現了一次的元素。 說明: 你的演算法應該具有線性時間複雜度。 你可以不使用額外空間來實現嗎? 示例 1: 輸入: [2,2,1] 輸出: 1 示例 2: 輸入: [4,1,2,1,2] 輸出: 4

思路:本來使用簡單的一個個數字比較做,後來才知道這是位運算(由於其他元素都出現了兩次,因此把全部元素亦或一下,結果就出來了)方面的題,我是菜雞阿,記得當時我導師面試我的時候也問過我這個問題,emmmm。

//小菜雞的初始程式碼(錯誤示例):時間複雜為O(n^2)
int singleNumber(int* nums, int numsSize) {
    int i, j;
    int flag = 0;
    for(i=0; i<numsSize&&flag==0; i++){
        flag = 1;
        for(j=0; j<numsSize; j++){
            if(i == j)
                continue;
            if(nums[i] == nums[j]){
                flag = 0;
                break;
            }
        }
    } 
    if (flag == 0)
        return -1;
    else
        return nums[i-1];
}

異或(exclusive OR,縮寫成xor)是一個數學運算子。它應用於邏輯運算。異或的數學符號為“⊕”,計算機符號為“xor”。其運演算法則為:a⊕b = (¬a ∧ b) ∨ (a ∧¬b) 即如果a、b兩個值不相同,則異或結果為1。如果a、b兩個值相同,異或結果為0。 同時異或也叫半加運算,其運演算法則相當於不帶進位的二進位制加法:二進位制下用1表示真,0表示假,則異或的運演算法則為:0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0(同為0,異為1),這些法則與加法是相同的,只是不帶進位,所以異或常被認作不進位加法。

// 異或示例:時間複雜為O(n)
int singleNumber(int* nums, int numsSize) {
    int result=0;
    for(int i=0; i<numsSize; i++)
        result = result^nums[i];
    return result;
}
  1. 給定一個非空整數陣列,除了某個元素只出現一次以外,其餘每個元素均出現了三次。找出那個只出現了一次的元素。 示例 1: 輸入: [2,2,1,1,1,2,3] 輸出: 3 示例 2: 輸入: [4,1,1,2,1,2,2] 輸出: 4

思路:我們把陣列中所有數字的二進位制表示的每一位都加起來。如果某一位的和能被3整除,那麼只出現一次的數的二進位制表示中對應的那一位是0;否則就是1,即按位計算每一位上1的個數,結果模3為1的那些位就是所求數二進位制1所在的位。

// 時間複雜度為O(n)
int singleNumber(int* nums, int numsSize) {
    int result = 0;
    for(int i=0; i<32; i++){
        // 左移動i個位置,相當於1*2**i
        int mask = 1<<i;
        int count = 0;
        for(int j=0; j<numsSize; j++)
            // 按位與,計算陣列中每個數字每位上1的個數
            if((mask&nums[j])!=0)
                count++;
        // 結果模3為1的那些位就是所求數二進位制1所在的位
        if(count%3 == 1)
            result = mask|result;
    }
    return result;
}
  1. 給定一個整數陣列 nums,其中恰好有兩個元素只出現一次,其餘所有元素均出現兩次。 找出只出現一次的那兩個元素。

思路: 這道題主要和1.類似,先將陣列中所有元素進行同樣的異或才做,但是這裡有兩個不同的數,所以需要從這兩個數的異或值反推得到結果。

整數在計算機中是以補碼(原始碼)的形式儲存的(不管基於何種語言),所以我們可以用n&-n( 等價於n & ~(n-1) )得到n的二進位制最右邊的一個1,得到兩個數不同的最低位。這樣對於這個為1的位置,肯定可以分辨出這兩個數,因為一定有兩個數在這個位置1個為1,另一個為0。所以遍歷整個陣列,和這個數做&操作(以之前求出的不同的最低位為標誌將全部數分成兩個組),一組為該位上是0的,另一組為該位上是1的。把兩組分別組內亦或,就可以得到我們要的兩個數。

正整數的原碼,反碼,補碼相同;負整數,反碼為原碼取反(除符號位),補碼為反碼加1(除符號為);且對一個整數的補碼再求補碼,等於該整數自身。

void singleNumber(int* nums, int numsSize, int *singleEnum) {
    // int singleEnum[2]={0, 0};
    int sum = 0;// 記錄兩個只出現一次的數的異或
    for(int i=0; i<numsSize; i++)
        sum = sum^nums[i];
    int lowest_dif = sum&(-sum);//得出兩個數二進位制不同的最低位
    for(int i=0; i<numsSize; i++){
        if((lowest_dif&nums[i]) == 0)
            singleEnum[0] = singleEnum[0]^nums[i];  
        else
            singleEnum[1] = singleEnum[1]^nums[i];       
    }
}

對於類似的題的其他直觀的解法:(1)我們很容易就能從排序的陣列中找到只出現一次的數字,但排序需要O(nlogn)時間;(2)我們也可以用一個雜湊表來記錄陣列中每個數字出現的次數,但這個雜湊表需要O(n)的空間。對比位運算幾乎都是O(n)的複雜度。